41

I found Why do C to Z80 compilers produce poor code? very interesting as it pointed out that C (which was designed to be an abstraction of a CPU for porting Unix) was not a very easy language to create efficient machine code from for the Z80. Apparently the same for 6502 where many dive directly in machine code. I read that the Sargon chess engine was very well suited for the 6502 due to the X and Y registers.

I know that the Z80 and the 6502 are very different, but I was wondering if there are any languages on a higher level than assembly which can generate compact and efficient 8-bit machine code by design for either of them (or any other 8-bit CPU from that era), and how this was achieved?

  • 24
    Do you consider FORTH high level? It generates very compact code that I'm guessing will compete with or beat C (on these processors) for speed. – Wayne Conrad May 29 at 14:26
  • 2
    @jon yes. Hence the question. Assembly takes much longer to write – Thorbjørn Ravn Andersen May 29 at 15:09
  • 14
    As I understand it, AVRGCC produces quite decent code. Before arguing that C produces inefficient code on 8 bit architectures, one should (a) clarify whether it's a problem of 8 bit archictectures in general, or specific 8 bit architectures like the Z80 or 6502; (b) clarify whether this is an issue of the language, or something compiler specific (modern cross compilers can throw a lot more resources at optimization, plus 20-odd years of compiler development); (c) check if you're forcing the compiler to be inefficient by, say, using int where a uint8_t would suffice. – Michael Graf May 29 at 15:52
  • 4
    @MichaelGraf AVR is a modern CPU architecture that was designed to support C and other high-level languages. It's in an entirely different league than the "classic" 8-bit CPU architecture we talk about here. – Ross Ridge May 29 at 20:47
  • 1
    @MichaelGraf Just think "Generate efficient code for a C64 or a CP/M-80 machine" – Thorbjørn Ravn Andersen May 29 at 20:48

12 Answers 12

44

One language that was popular on early 8-bit micros, including those that used the 6502 CPU, was Forth. Forth is exceptionally good for this use case, and superior to a C compiler, because Forth can make more efficient use of the 6502's hardware stack. Forth lacks any sophisticated methods of dealing with parameters. Everything is passed through the Forth stack and procedures just deal with the stack for both their input and output. This means the language doesn't require much from the CPU in terms of addressing modes or spend any time doing sophisticated effective address calculations.

Additionally, Forth provides a somewhat different paradigm than C in that it requires a program to be built up from very primitive and efficient units known as "Words" in Forth. By combining the primitive words into ever more complex combinations, the program is built up in a way similar to Functional Programming languages. This ensures Forth is very simple (and fast) to compile, even on 8-bit machines, and that the results execute very efficiently, given that the lowest level Words were coded to be efficient on the CPU.

According to some 6502 Forth users, the typical overhead incurred by Forth programs vs. similar functionality in Assembly is about 25%. And various Forth compilers for 6502 have been implemented in as little as 1.5 KiB. This fact makes Forth likely the only language compiler you will find running from an 8-bit computer ROM cartridge. So, it is both the low overhead of the compiler AND the efficiency of the resulting code that made it a favorite of early microcomputer programmers seeking something more "productive" than Assembly language.

| improve this answer | |
  • 3
    FORTH was available as compiler language? – Martin Rosenau May 29 at 15:09
  • 3
    @MartinRosenau Yes. Generates machine code and no interpreter or runtime is required. – Brian H May 29 at 15:13
  • 5
    @moonwalker Forth worked quite well on the Z80 too. There's the Jupiter Ace that had a bulit-in Forth instead of BASIC. – OmarL May 29 at 16:03
  • 3
    I worked (professionally) with Forth in the mid-80s, and don't remember any compilers that compiled to machine code. I think it would actually be less efficient in terms of space to do this, as well as in terms of time if it was implemented as subroutine calls. The standard Forth implementation stored a word's definition as a series of addresses, and the execution engine would use a jump instruction to go to the next address. Not saying it didn't happen, but I don't see why anyone would. – kdgregory May 30 at 11:54
  • 2
    @JeremyP: If a Z80 Forth system were to use the system stack and HL for the operand stack (top of stack is HL; everything else is on the stack), code to push a constant would be simply "PUSH HL / LOAD HL,xx". Four bytes, including the two-byte constant. "+" would be "POP DE / ADD HL,DE". Two bytes. Operations that need to be handled by called functions would be a bit trickier. If one wanted to do a subtract that way, it might need to "POP DE / POP BC / AND A / SBC HL,BC / PUSH DE / RET". Two extra instructions to manage the return address, but if most operations avoid fn calls... – supercat May 30 at 18:51
20

C can be greatly improved as a language for the 6502 and Z80, as well as micros like the PIC and 8051, if one abandons the notion that implementations must provide for recursive subroutine calls, and adds qualifiers for things in zero page or pointers that are limited to accessing such things, and (for the Z80) adds qualifiers to identify objects that are known not to cross 256-byte boundaries.

Ironically, platforms like the PIC and 8051 which can't really support recursion at all and would thus seem unsuitable for C end up having better C compilers than those like the Z80 and 6502 which can barely support recursion, and thus generate code which is reentrant but inefficient instead of efficient non-reentrant code.

| improve this answer | |
  • 6
    This is why Small C (and its many flavors) flourished in the 8-bit world. – Jim Nelson May 29 at 20:05
  • 1
    @JimNelson: Support for recursion is one of the biggest obstacles to efficient code generation. BTW, another useful feature on the 6502 I've not seen in compilers would be an ability to use a "register" qualifier on arrays that would only be accessed via the subscripting operator rather than member-type pointers. Such arrays, if 256 items or less, could be stored more efficiently with all low bytes together and all high bytes together, than as low-high pairs. – supercat May 29 at 20:18
  • 1
    So you think of a subset of C? – Thorbjørn Ravn Andersen May 29 at 20:21
  • 1
    @ThorbjørnRavnAndersen: C's syntax isn't great, but I think it would be adaptable to the purpose of programming an 8-bit micro. – supercat May 29 at 21:26
  • 2
    @supercat To my understanding the only language which really had a standard back then for microcomputers were Standard Pascal. I fully understand why e..g. Turbo Pascal chose to rework the string support. I am interested in any high level language which translated really well to machine code. – Thorbjørn Ravn Andersen May 30 at 18:15
20

I know that the Z80 and the 6502 are very different, but I was wondering if there are any languages on a higher level than assembly which can generate compact and efficient 8-bit machine code by design, and how this was achieved?

Well, a prime candidate would be Ada.

It was a specific design goal for Ada to produce good code for tiny and 'odd' microprocessors (*1). Two basic approaches enabled this:

  • the language itself was as non-assuming as possible, while at the same time
  • offering tools to specify certain workings as detailed as possible - where needed -
  • separating this to a great degree from generic code.

The high abstraction separates it from 'lower' languages like C or FORTH which are both built around certain assumptions about how a processor works and what functions it offers. In fact, C and Forth are great examples of two major pitfalls:

  • Expecting a certain low-level behaviour of a CPU and
  • ignoring high-level functions offered by a CPU

C for example is built on pointers and the assumption that everything has an address and is a series of bytes which can be iterated over (and may be structured further, but that can be ignored at will). CPUs with multiple address spaces or object storage or different understanding of data handling will inherently end up with less than desirable code.

The /370 is a great example here. While (register-based) pointers are an essential feature, the memory pointed to is handled as a block (or structure) with sub-blocks (fields) that can be manipulated with single instructions, not loops (*2). C-code forcing iteration onto a /370 can easy degrade (local) performance by a factor of 100 and more (*3).

Forth on the other hand is at its core built around the idea of a stack (or multiple stacks) and the ability for threaded code. Effective (stack) pointer handling and fast (and simple) moves to and from the stack are essential for performance. Both issues that 8-bit CPUs aren't inherently good at. The 6502 may have 128 pointers, but handling them is ugly. Indirect jumps, such as those needed for threaded code, are non-existent. Thus, fast implementations rely on self-modifying code. Then again, it is only marginally better on an 8080/Z80.

Like C, Forth ignores higher-level function offerings, or has a hard time using them. Unlike C, it's a bit more open to changes in low-level behaviour.

Both languages are maybe higher than assemblers can operate on a more abstract level - if used carefully - but are not inherently abstract. They assume certain workings. If these are not basic machine instructions, performance will suffer.

A 'real' high-level language should not make such assumptions. Here, Pascal is a better candidate, as it assumes next to nothing. As a result, there are compilers for either line, 6502 and 8080/Z80, producing quite good code. I guess Turbo-Pascal for CP/M doesn't need any further introduction. On the 6502 side (Apple, Atari, Commodore) Kyan Pascal was considered a great way to work in high-level languages (*4).

Which brings us back to the original question, how to achieve good code performance on a wide range of machines:

  • Don't expose any low-level working to the programmer.
  • Have the compiler cover it.
  • Have the programmer define the intended result, not the way it is achieved.

Essentially the goals set for Ada :)


P.S.:

... on a higher level than assembly ...

Serious? That statement feels quite offending :)

Assembly can and often is already on a higher level than some other languages. Assembly is the essential prototype of an extensible language. Everything can be done and nothing is impossible.


*1 - Note the 'produce' clause, having the compiler run on such machines is a different story.

*2 - It's always helpful to keep in mind that the /370 may have spearheaded many modern concepts, but it was designed with a punch card in mind. A punch card is a record, maybe pointed to by a register, holding information (fields) at fixed offset with fixed length. The whole instruction set for character (byte) manipulation is built to fit. No need to loop over two fields to move, compare, translate, pack or even search within, the most basic instructions (MVC, CLC, TR, PACK, TRT) already take care to handle fields at once.

*3 - This was a huge problem when C first became requested by users and implemented. By now compilers have evolved, and more importantly, CPU designers have added quite some 'changes' to cover up for the inefficiency of C.

*4 - Its only fault was its late 'birth' - too late to make a major impact.

| improve this answer | |
  • 1
    I agree, Pascal would be the obvious choice, but Ada is a good fit. – Mark Williams May 29 at 19:13
  • 2
    Thank you for being thorough. I'm sorry if you are offended by asking about a higher level than assembly - wikipedia says "In computer science, a high-level programming language is a programming language with strong abstraction from the details of the computer. ". If your assembly language code abstracts strongly from the details of the computer, is it still assembly or some higher level language implemented in assembly macros? – Thorbjørn Ravn Andersen May 29 at 20:17
  • 1
    Ada works as long as you relinquish the idea of being able to compile a program on the target architecture. My University created an Ada compiler which was by no means fully featured but required an IBM PC with 4Mb of extra RAM. – JeremyP May 30 at 11:17
  • 1
    By the way, the 6502 does have an indirect JMP but Forth requires two stacks. Implementing the data stack even using a zero page pointer is going to result in pretty slow code. – JeremyP May 30 at 11:23
  • 2
    @davidbak And that's why, to me, it is close to a perfect language for 8 bit systems. The application wasn't anything remarkable. Just a controller for an operator panel with two membrane keyboards, several seven-segment displays, a bunch of LED and two serial. Soft and hardware was rather simple. The nice part was an ability to constant self-testing of all items with reporting of any (upcoming) failure as well as denying usage in case of issues detected. It was part of a medical device, so higher standards. That's also the reason why the dreaded 8051 was used. It was considered proven working – Raffzahn May 30 at 18:16
9

"Forth" was the first name that jumped to my mind. Another is Action!, an Atari 8-bit-specific language. (Its manual can be found on the Internet Archive.)

Action! is a structured Algol-inspired language that borrowed constructs from other languages (in particular, it offered C-like pointers and arrays) and native types that mapped cleanly to the 6502's memory model. Techniques that usually required assembly were possible, such as positioning code/data and trapping interrupts. Action! kind of stood between a full-featured macro assembler and a high-level language like Pascal. (That it didn't have native floating-point support or recursion is a hint of how pared-down it really was. This page has a nice summary of its limitations.)

I couldn't find hard numbers showing it was more efficient or faster than the Atari C compilers of the time, but this article from Hi-Res magazine shows Action! finishing a Sieve benchmark in the approximate time of a Z-80 C program.

Action! also offered a surprisingly full-featured IDE before the acronym was coined: Integrated full-screen text editor, in-memory compilation (which made it quite fast), and a monitor for debugging. Action! showed me how good tools make a big difference in the edit-compile-debug loop.

| improve this answer | |
8

Ada for cross compilation; though there WERE native Ada compilers (e.g. Janus Ada, with a Z80 (Ada-83) release here and reviewed in 1982 here) it was stretching the capabilities of a 64kbyte machine. Side note : the response to review was by Randy Brukardt; in 2020 he is still selling Janus Ada and actively contributing to the comp.lang.ada newsgroup!
So, Gnat (utilising gcc and soon LLVM) can run on any decent host, and optimise pretty well for small targets - nowadays, AVR or MSP430. Ada is in some ways easier to optimise than C.

But one other candidate worth mentioning for native compilation would be Modula-2. A much smaller simpler (and yes, more restricted) language, rather in the Pascal mode, but much more amenable to compile on a decent Z80 system. I had the FTL Modula-2 compiler running on a Z80 CP/M system.

I don't remember specific benchmarks on Z80, but on slightly larger 8086/8088 systems (where "small model" executables were 64K) the JPI Topspeed Modula-2 compiler of the 1980s was probably the most efficient compiler for 8086 of any language in the DOS era.

| improve this answer | |
  • I ran turbo modula from Borland. A rather nice product but I did not look at the machine code. – Thorbjørn Ravn Andersen May 30 at 16:14
  • 1
    @ThorbjørnRavnAndersen I believe that was bought by JPI and developed into Topspeed. – Brian Drummond May 30 at 16:15
6

The main problem for high-level-languages on these platforms, and especially the 6502, is the small hardware stack. 256 bytes does not give one much room to work with for languages that intend to push large activation records on the stack.

As others have noted above, the solution is to remove recursion from your language definition, and in a more general sense, any "local" information.

Also worth mentioning, in the 1970s and early 80s when these machines were the bomb, the language all the cool people were working with were the many variations of ALGOL. Most mainframe systems had a "systems programming language" based to some degree on ALGOL layout, and Pascal once that became, effectively, the "new ALGOL". C did not become the universal solvent until the 16/32 bit machines had been in the market for some time.

So for instance, on the Atari you had Action!, an ALGOL-derived language with no recursion. This not only reduced the size of the stack use, but also greatly reduced the complexity of a proc call, you basically just did the branch. This later bit remains a topic of discussion to this day, as in Swift where Apple tries to convince you to use struct instead of class to reduce call overhead.

Raff mentioned Forth, which was designed as a multi-platform language that used its own stack structure to provide C-like capabilities on machines that lacked the requisite hardware support. While I guess it was a success in that respect, I recall trying to program in it and having feelings much the same as drinking way too much cheap gin.

| improve this answer | |
5

I suggest PLASMA (https://github.com/dschmenk/PLASMA), a C-like language that compiles to interpreted code. It has a much higher code-density than assembly language, and it's much faster than FORTH.

| improve this answer | |
  • 2
    I suppose you mean, faster than interpreted Forth. And not faster than compiled Forth? – OmarL May 30 at 7:22
  • That's the third PLASMA programming language I heard of. The most well-known is probably Carl Hewitt's PLASMA language he designed to illustrate his newly-invented Actor Model of computation, and a member of the MIT AI family of languages (LISP, PLANNER, Microplanner, PLASMA, Scheme). The second one I forgot. And just now I found this: plasmalang.org . – Jörg W Mittag Jun 1 at 16:20
  • @wilson It could compete favourably with even compiled Forth, based on the examples of compiled Forth that I have seen, but maybe I've been looking at the results of a poor compiler. – peter ferrie Jun 5 at 22:51
4

It only has to do with the effort put into the code generator back-end. C is an abstract language, it doesn't need to directly reflect what the machine is doing. But this is the sort of stuff that would be state-of-the-art in 2020, and would require significant investment. There's nothing inherently special about Z80 of 6502 in this respect - only that the impedance mismatch between some platforms and the code generator back-ends is very high. For Z80 and 6502 it wouldn't matter what the language is, because the specifics of the language are far away and dissolved by the time the intermediate representation gets to the optimizer and code generator. Any high-level compiled language would be just as bad on Z80 and 6502 as C is, pretty much.

We're spoiled with excellent modern compiler back-ends. The trouble is that they are commonplace that everyone thinks it's "easy" work. Not at all. They represent man-decades of effort if someone were just to reproduce them.

So, you can get excellent Z80 and 6502 code out of a C compiler if you hire a couple LLVM back-end experts out from Apple and Google, pay them the going rate, and let them at it for a couple of years. A couple million dollars is all it'd take, and you'd grace the world with absolutely amazing Z80 and 6502 code produced from both C and C++.

So: I'm sure the results would be excellent- but it requires lots of effort. It's the sort of effort that historically has not been expended by even major silicon vendors, with exception of Intel, Digital and IBM. Zilog's own compilers (all of them, doesn't matter what year was the release) are junk when you compare what they manage to cough up to x86 or ARM output from C code passed through Clang and LLVM, and all the man effort put up by, say, Zilog and Motorola compiler teams throughout the 70s, 80s and 90s, all together in total, was completely eclipsed by the man-hours that went into, say, Clang+LLVM in the first decade of the existence of both projects. Zilog's and Motorola's marketshare back when they still had plenty of it absolutely didn't improve matters here: they were a bit too early and the everyday techniques used by e.g. LLVM weren't available and/or they required so much memory and CPU cycles to run that it wasn't feasible to offer such products to wider audience, because you pretty much needed a heavy minicomputer or a top-notch workstation to do this sort of work.

| improve this answer | |
  • 1
    Do you have actual experience with 8-bit cpus? – Thorbjørn Ravn Andersen Jun 28 at 10:35
  • 1
    Semi-regrettably so. I'd say half of the code I wrote over the last 3 decades of me writing code was for 8-bit CPUs. When I bash Zilog and Motorola compilers, it's based on experience all too intimate :/ But in short: If humans can write acceptable Z80 assembly, so can a C compiler that got sufficient amount of money and talent dumped into it, under leadership of a suitable luminary. That's "all" there's to it, at the end of the day: how many $$$ were spent (or spent not) on that problem - and were they spent productively. – Unslander Monica Jun 29 at 2:14
  • 1
    Getting good C or Pascal-language performance requires foregoing recursion in all cases where it isn't needed, and using a linker that can statically overlay automatic objects ("local variables") used in different functions. I don't think LLVM is really set up to accommodate such things. Further, modern compiler back-end optimizers seem to have baked-in assumptions about the relative costs of various actions, and generate sub-optimal code for platforms where the actual relative costs of actions don't fit those assumptions. – supercat Jun 29 at 16:46
  • @ReinstateMonica: An annoyance I have with the design of C is the lack of byte-based pointer arithmetic operators. When using arrays of 16-bit values in machine code, especially on the 16-bit 8088/8086, or even more so on the 68000, it often makes sense to have indices step by two to exploit the [bx+si] or [si+const] or @(Ai+Dj.w) addressing modes, but there's no nice way to express such concepts in C. – supercat Jun 29 at 17:21
  • 2
    "We're spoiled with excellent modern compiler back-ends. The trouble is that they are commonplace that everyone thinks it's "easy" work." This seems to be almost impossible to explain to people who keep saying that this or that optimization just should be there, no big deal. I really appreciate you saying this so explicitly. – introspec Jun 29 at 20:24
3

Despite the other answers posted here, Forth generally performs significantly worse on the 6502 than an optimizing C cross-compiler like CC65. In tests I did comparing it to Tali Forth 2 for the 65C02 [1], which generates the fastest type of Forth code called STC, Forth code is sometimes on par with the C equivalent but more often 5-10 times slower. As far as I can tell, these are the main reasons:

  1. All values pushed on the stack in Forth become 16 bit, which takes the 6502 a lot longer to manipulate than 8-bit values. C, on the other hand, has 8-bit types which are much faster to work with.

  2. Forth words constantly adjust the data stack as they push and pop things, while C functions tend to do most of the stack allocation at the beginning and end of a function, which is much more efficient.

  3. 6502 Forths don't generally do any optimization, even when enough information exists at compile time to do so. Something like "drop 5" in Forth will increase the stack pointer to do the drop then immediately decrease it to push the 5, so you get the useless series INX / INX / DEX / DEX. CC65 optimizes this type of inefficiency out in some but not all cases.

  4. 6502 Forths also don't optimize for constants. CC65 outputs more efficient assembly for something like "foo<<3;" than "foo<<bar;" since the number of shifts is known at compile time. Forth generates the same code in both cases, always using the most compatible but slowest version.

  5. Constraining the programmer to only modifying the top levels of the stack produces less efficient code. For example, you can't step over the first item on the stack and add something to the second. The equivalent "swap 5 + swap" wastes time on the two swap operations to get the value to the top of the stack and back into second place, while C can just directly modify any item on the stack.

CC65 is not perfect, but you're unlikely to get anything near as fast as that without writing the assembly yourself.

[1] http://calc6502.com/RobotGame/summary.html

| improve this answer | |
  • 1
    Do you have a link to the programs you compared? – UncleBod Jun 20 at 17:20
  • @UncleBod, I've been working on a web page to show the tests and my data. I'll try to post it here in the next day or two. – Joey Shepard Jun 20 at 17:22
  • @UncleBod, link to the test I did: calc6502.com/RobotGame/summary.html – Joey Shepard Jun 30 at 12:55
2

I know that the Z80 and the 6502 are very different, but I was wondering if there are >any languages on a higher level than assembly which can generate compact and efficient >8-bit machine code by design for either of them (or any other 8-bit CPU from that era), >and how this was achieved?

I've been working on my own high-level language "Higgs" which targets 6502,65C02,68000,68040, RISC DSP and recently started working on Z80 backend.

The output (build script called from within Notepad++) is an assembler file that is then fed into the local assembler/linker of the respective platform.

The feature list of the language depends directly on the target platform's abilities. Each HW target has different set of unique features, dictated by the addressing modes / asm capabilities of the platform. Arrays on 6502 are very different than arrays on 68000 or DSP RISC.

Each target however supports global/local/register variables, global/local constants, structures, arrays, functions (with optional parameters), loops, conditions, nested blocks (helps with formatting and namespace pollution), 3-parameter math expressions, signed math (if present), increment/decrement (var++, var--).

My basic rule is that I never include a new feature unless I can guarantee that the code generated by my compiler is identical to the code I would write manually, directly in ASM.

From experience of writing my own game in it (~25,000 lines of Higgs so far), it's exponentially faster to write/debug/test new code compared to ASM. Less than 0.01% of code is still written in ASM, the rest is Higgs.

I will be adding Z80/Next backend soon.

If you could only have 3 features that would increase your productivity, this is what gives you most return:

  1. conditions
  2. math expressions
  3. scope-based variables/constants {}

Here's an example (68000 target: hence d0-d7/a0-a7 registers, .b, .w, .l sizing, etc.), showing how high-level it is (compared to ASM) and that it really feels almost like C, and is thus very easy to come back to, after 6 months, and quickly understand and adjust the code (unlike hand-written ASM that mostly evokes deep WTF feelings):

Render_LaserShots:
{
    local long lpMain

    {   ; Player LS
        colorQuad = #$FFA080
        SLaserShot.InitRegister (LaserShots)
        loop (lpMain = #MaxLaserShots)
        {
            if.l (SLaserShot.IsActive == #1)
            {
                d1 = #0 - SLaserShot.X
                d2 = SLaserShot.camY
                d3 = #0 - SLaserShot.camZ
                SetCamPos32 (d1,d2,d3)
                Render_obj3DList_Object (LaserShotMeshPtr,#PolyCount_LaserShot)
            }
            SLaserShot.Next ()
        }
    }
    {   ; ShootingEnemy  LS
        SEnemy.InitRegister (MainEnemy)
        if.l (SEnemy.State == #AI_STRAFE)
        {   ; Only Render Enemy's LS if he is active
            colorQuad = #$40FF40
            SLaserShot.InitRegister (EnemyLaserShots)
            loop (lpMain = #MaxLaserShots)
            {
                if.l (SLaserShot.IsActive == #1)
                {
                    d1 = #0 - SLaserShot.X
                    d2 = SLaserShot.camY
                    d3 = #0 - SLaserShot.camZ
                ;   print3 (d1,d2,d3,#50,#20)
                    SetCamPos32 (d1,d2,d3)
                    Render_obj3DList_Object (LaserShotMeshPtr, #PolyCount_LaserShot)
                }
                SLaserShot.Next ()
            }
        }
    }
rts
}
| improve this answer | |
  • It will be very interesting to see what you end up with languagewise. – Thorbjørn Ravn Andersen Jun 21 at 14:00
  • 1
    Note that a good optimizing compiler can do tricks in assembly that may not be what you would write by hand. – Thorbjørn Ravn Andersen Jun 21 at 14:01
  • @ThorbjørnRavnAndersen : True - once the code is done, there are so many things you can do, to butcher the code, make it faster, remove CLC/SEC here and there, or notice you could (ab)use your index registers in a way that will make them directly reusable in next stage. But, that creates a totally unmaintainable code. Now, I've done that in past,but for me, it's of highest importance to be able to come back later and adjust the code. Which is impossible, once you butcher it manually. Since the code is high-level, I have zero qualms about discarding it altogether (unlike hand-butchered code) :) – 3D Coder Jun 29 at 7:56
  • @ThorbjørnRavnAndersen: Short-term, my ToDo list shows in Top 3: function return values and classes. Those two seem to promise to bring most productivity. After that, I would probably focus on multi-platform codebase - e.g. writing game code once, but reusable on multiple HW targets. – 3D Coder Jun 29 at 8:05
1

For 8080, 8085 and Z80, possibly PL/M. That generated exactly what you told it. Also, it had special I/O instructions. With most other compilers, you had to call

output(0x20, 0x90)

but in PL/M it was built in

output(0x20) = 0x90

would generate the out instruction. There was a similar input instruction. The part of PL/M that always caught C programmers out was that even numbers were false and odd numbers were true. This gave rise to PL/M86 and PL/M286.

The use of Forth varies

  1. as a compiled language
  2. as a concept with a generic interpreter
  3. as a concept using indirect threaded code (https://en.wikipedia.org/wiki/Threaded_code#Indirect_threading) with a home-brew interpreter
  4. as a concept using knotted code (also known as token threads) with a home-brew interpreter.

I've seen 3 and 4 but not 1 or 2. Option 3 and 4 is normally used to reduce the code size but the program runs more slowly than if it were written in straight code. In the late 70s and early 80s, when information was obtained from journals, it wasn't easy to find a Forth compiler so most of the time it was a home brew version and everything was written in assembler.

| improve this answer | |
0

This is my experience with C on z80 and 6502:

Zilog Z80/z88dk

the code generated is pretty decent, not as good as hand written assembly but good enough for lots of purposes. One advantage on Z80 in relation to C is existence of IX/IY registers that are used for local variable access/parameters passing. Of course they aren't as efficient as register parameters but it still works good with C paradigm. I tested switching to static variables there was a difference but small.

6502/cc65

I'm not familiar with 6502 assembly too much but aware of general architecture. When I compiled the code with and without static variables for 6502 the impact was very big (IIRC up to 50%) I can't compare with hand written code since I have not experience.

Bottom line there is a big difference in processor architecture. Zilog Z80 is much more C friendly, it as decent stack, index registers that allow quite straight forward implementation of many C paradigms, calling conversions etc. While 6502 is much more limited in implementing reenterable code or using stack bases variables.

| improve this answer | |
  • 1
    My experience is that on the Z80, there is a huge cost difference between the cost of entering a function that uses no parameters or automatic objects, versus calling one that uses some. Once a function is entered, the cost of accessing 8-bit automatic objects using IX addressing isn't too totally outrageous, but accessing 16-bit values using IX often costs at least twice as much and sometimes more. By contrast, 16-bit instructions like mov hl,(addr) or mov de,(addr) are less than 25% more expensive than the 8-bit mov a,(addr). – supercat Jun 21 at 21:57

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.