John West wrote:
Much of the software written in the 70s was not primitive. The 6502 ISA was designed for hand-written assembly code, fast memory (relative to the CPU clock), what they could fit on a chip, and what microprocessor programmers were used to.
Compared to today's software, which tends to be *strongly* object oriented and even component oriented, it
is primitive. The 6502 ISA makes indirect vectored jumps off a base pointer register absolutely *painful*. The 6502 ISA has little to no support for single-stack, frame-based languages. Etc.
The 65816 corrects the latter deficiency acceptably, but still does not address the former. There is still no truely fast (< 8 cycles) method of invoking an object method by way of a virtual method table. The fastest approach is to compile the following code which runs inside the class definition:
Code:
__do_method_entry_point:
jmp (vtable,x)
vtable:
dw method1
dw method2
dw method3
..etc..
where X is loaded with the method ID (in reality, an offset into the vtable) before-hand. Since an application really won't know where __do_method_entry_point is located first, it must first query the object for this code (since different classes of objects have, by definition, different vtables):
Code:
ldy #0
lda (obj),y
sta foo+1
iny
lda (obj),y
sta foo+2
ldx #method_id
jsr foo
foo:
jmp $0000
So, assuming we can cache vtable pointers as an optimization, we still need to have the initial JSR (6 cycles), the absolute JMP (6 cycles), the JMP (,X) (another 7 cycles), and finally the RTS (another 6 cycles). All told, that 37 cycles for a method call -- very expensive, very cumbersome, very primitive.
In contrast, most other CISC processors make this process utterly trivial. And RISCs, while it takes more instructions than most CISCs, do it with great speed (typically only 3 to 4 cycles, max).
This would not have happened in the 6502 ISA had the designers recognized and developers wanted to code such software. Back then, object orientation was still in its infancy (hell, even
modular programming was still in its infancy, having gotten over the hubbub of Pascal and
structured programming), what with Smalltalk still behind the closed doors of Xerox Parc, and Simula never really having caught on except in Holland and Finland. Even then, Smalltalk was dynamically dispatched, not statically like the above, which incurred still more run-time overheads. You can use polymorphic inline caching to help speed up method dispatches by comparing method IDs to class IDs and direct-jumping to method implementations, I suppose (see code fragment below for an example), but it still results in substantial runtime losses, especially for the case where a class is infrequently accessed to start with, but is called with increasing frequency later on.
Code:
; This code is dynamically generated by the language run-time environment.
client_do_method_X:
ldy #00
lda (obj),y
sta clsID
iny
lda (obj),y
sta clsID+1
cmp #CLSID_1_HIGH
bne .try.class.2h
lda clsID
cmp #CLSID_1_LOW
bne .try.class.2l
jmp method_X_for_class_1
.try.class.2l:
lda clsID+1
.try.class.2h:
...etc.. you get the idea.
Yeah, that's pretty bulky, and awfully primitive in my eyes.
Granted, you can state that any program you can write in an object oriented language, you can also write in a non-OO language. I do this all the time myself. But, it's also true that any program you can write with multithreading can also be written as a pure event-driven, single-threaded application too. But sometimes, there are advantages to choosing one over the other. Maybe the multithreaded application uses less memory, or is more conceptually correct (and thus easier to maintain from a coding and debugging point of view). Likewise, programming in an OO language, or even just using OO methodology, often results in more correct code. GUI programming, for example, is a *natural* for OO technology, as is simulation software. So I hope to nix that whole "who needs OO anyway?" argument in the bud right now before it becomes the non-issue that it is.