[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: 6502 illegal opcodes questions
mdj wrote:
Michael J. Mahon wrote:
Of course there is a need for higher levels of abstraction.
But much of the discussion here has been driven by "strongly typed"
and "restricted" languages and semantics vs. "weakly typed" and
unrestricted (nonportable) languages.
And much of the argument against being that strongly typed yields
inefficiency, a point I strongly disagree with.
And I support your disagreement. My issues are different,
and relate to the fundamental inability of any language to
save programmers from undisciplined tendencies.
The presumed superiority of the former is that it either:
1) makes it harder to do bad things accidentally,
2) makes it harder to do bad things intentionally,
3) makes it easier to do good things accidentally, or
4) makes it easier to do good things intentionally.
I note that 1) and 3) (the unintentional flavors) can be readily
supported, and can be construed as support for relative newbies.
Not just newbies, though. Plenty of good programmers have used
techniques that over time have proven to be problematic. Some of them
realise the problems and stop using the techniques, some of them don't.
It's common to end up in a heated discussion with a fine engineer who
doesn't want to change an obviously flawed approach because they cannot
see the future benefits of doing so.
And, ironically, sometimes they turn out to be right.
I must share with you that, as you may expect, I had a phase of
my professional life when I was a language and compiler designer.
During that time and since, I have had many opportunities to discuss
language design issues with "experts", and I find that they are almost
universally adamant in their positions, perhaps hardened by long debate.
I have come to characterize this adversarial, detail-focussed,
"destroy the woods to save a tree" attitude as that of a "language
lawyer" who is more intent on following a rule than on achieving
a real objective. It is quite amusing when it is not obstructing
something real that needs to be accomplished. I also think it is
why compiler/language "types" are generally regarded with disdain
by great system programmers, who see them as not being team players.
They are more devoted to their "principles" than to getting the job
done. And they seem oblivious to the fact that their principles are
really just the "tradeoff du jour".
Look at the history of language design--filled with religious fervor
and yet as changeable as fashion. The longest lasting phases have
been the most pragmatic, and the shortest the most theoretical.
I no longer await the language that will save us all. In my
experience, our salvation is up to us and our wits. We may, from
time to time, find a tool or method that we find useful, but there
is no ultimate tool or method. To imagine that there is is itself
a great danger, since it makes us more dependent on tools and less
masters of our own designs.
I expect that you will say that wonderful language "X" actually
improves our mastery of our designs, but I say that it does so only
by constraining our thought in ways that are superficially beneficial
but which are ultimately limiting--as only time will tell.
A great designer can do great things in any language, but it will
be much harder to do all the great things he can do in just one
language. Imagination will not be contained.
You argue that a more formal notation prevents accidental errors,
and so improves the result. But I argue that it also obstructs
brilliant design strategies containing elements considered harmful
by the language designer.
It is much more difficult to make a case for 2), since intention
knows no bounds, and I think we have all seen cases where the
finest language has been horribly abused.
Can't argue with that. It's certainly possible in any environment to
exploit it horribly. It's true though that as the level of 3) goes up,
the frequency of 2) goes down. You're much less likely to try hard to
do do something that has an obvious easy way.
But then, it certainly does happen ;-)
Indeed--who can fathom all the reasons for someone
wanting to "beat the system"?
It is certainly possible to write non-portable code in any sufficiently
complex system, simply by exploiting some irregular behavior--and there
will *always* be some irregular behavior to be exploited. ;-)
The hardest part of the problem, and one yet unsolved by language
design.
Not only unsolved, but unsolvable in any finite system.
A classic source of behavoral irregularity is in module initialisation.
You frequently situations where a module is initialised according to
it's documentation, only to find out you didn't do it exactly right,
and ended up depending on some non-determined default within the
module, and later version break your system. Regardless of the level of
typing involved, this one can surface. It's a current topic of research
to figure out solutions to this issue.
A heuristic that I used to employ was to initialize all data memory
to hex FF, so that (on the machines in question) it would represent
invalid data and an invalid address.
A related and generally applicable approach is to initialize all
data memory to random values, so that data initialization errors
will manifest as nondeterministic behavior.
Some machines (the 7044, for example) had a word parity bit which
could be stored as "bad". A subsequent read reference would trap,
while a store would proceed with no error. This made an excellent
"uninitialized data" trap--but few machines support this today.
Burroughs stack architecture machines also had tags associated with
memory words, which would allow the detection of invalid references,
but this type of checking is currently out of favor.
I realize that there are more complex initialization issues than the
simple "uninitialized data" one I have discussed, but it's a start. ;-)
It is also possible to write spaghetti code in any language, if
only by writing it as a state sequencer in a looped case statement.
This is actually one of the best examples of where language design
falls down, and it's often construed that you need to rely on
old-school constructs to build efficient state machines. While this is
essentially false, I'd like to see better language support for state
machines in the first place. It's odd that such a fundamental facet of
computer science has such poor abstractions in place around it.
Think about it. Any Turing complete language can be used to write
a simulator for any other Turing complete machine. There is no way
to prevent this without sacrificing the universality of the language.
Case 4) is the most interesting, since it offers the real possibility
of improvment by raising the level of abstraction. It is worth noting,
though, that most abstractions employed in more modern languages were
developed by designers working in less restrictive languages, doing
explicitly what was later encapsulated implicitly.
Therefore, a less abstract language, far from preventing a good designer
from doing beautiful abstractions, actually provides the medium for the
abstractions to be created and prove their worth. It remains to be seen
whether abstract languages like Java will similarly give birth to new
levels of abstraction "beyond objects".
Design Patterns is an excellent example post-object abstraction. While
there's a lot of hype around the concept the original text, entitled
"Design Patterns" is an interesting read. Essentially, the authors
studied the code of several large "successful" systems built in OO
languages, and discovered many recurring patterns which they distilled
into generic descriptions and templates as solutions to commonly
recurring problems.
If you've heard OO programmers uttering terms like Singleton, Facade,
Builder, Adaptor, this is where these terms come from. It's evolved
into a 'language' for developers to talk about complex design ideas.
For the moment, it's a language for humans rather than machines, but
it's only a matter of time.
I only note that there are successful patterns which are *not* made
of OO concepts, as well. Perhaps some of the most useful ones...
If it were not for languages with explicit pointers, all our current
"object oriented" languages would never have come to be. The first
such programs were written in assembly language, and were far more
versatile and efficient in their realm than comparable programs
written today. (Sutherland's Sketchpad comes immediately to mind.)
Sure. It's worth pointing out though that explicit pointers aren't
necessary to achieve efficient designs, just as, on 32 bit platforms
anyway, there's no need to write position dependant code. If you're
targetting a 6502 or even 68000, you have to make some compromises, but
those restrictions simply no longer exist, and the need for techniques
that allow for the restrictions are gone with them.
But all efficient (?) implementations of objects rely heavily on
pointers. And "simulated" pointers are still pointers, just less
efficient.
Position-dependent code is irrelevant, since in a 64-bit virtual space,
every procedure/module ever written can be assigned a unique location!
Virtual memory makes relocation unnecessary (though perhaps occasionally
useful).
As we've discussed, it's possible to write portable code in languages
like C, provided you're *very* careful. Good designers can achieve
this, and not compromise efficiency either. The obvious point is
though, what can the good designer achieve if he doesn't have to expend
unnecessary energies being conscious of issues that can be solved by
language design?
You have *way* too much faith in language designers! ;-)
They make serious errors every day--and on behalf of thousands of
victims, er, users.
Did you actually *read* Kahan's criticisms of Java numerics? (I
realize that Kahan is a brilliant and compulsive polemicist, but,
dammit, he also has this nasty habit of being *right* sometimes. ;-)
His point is that what Java's designers didn't understand, they
botched. Actually quite understadable. And he gives cogent
examples of the consequences and simple recommendations to fix
the problems--which were not acted upon!
When I first saw Java, and heard it proposed as a "universal"
language, I noticed that it had vectors of vectors, rather than
multidimensional arrays. I knew immediately that this would be
a huge performance issue for numerical programming, since I had
the pleasure of working with Burroughs machines that had exactly
the same limitation--and the same fundamental inefficiencies.
Further, though abstraction is essential, the best designers are
characterized by a much wider "abstraction bandwidth" than is
common, and their ability to span many levels of abstraction in
their thought is what allows them to create *efficient* designs,
rather than just "correct" designs. (BTW, designers with "high
bandwidth" are always in short supply, and much of todays software
reflects their absence.)
I agree. I do believe though that such designers achieve more with
better languages and tools.
Only to the degree that those tools do not limit their thought--and
there is no tool that does not impose its limitations.
"Thinking outside the box" sometimes means thinking outside the
capabilities of your current tools. Often the *real* brealthroughs
come from just this kind of thought.
I have been frequently appalled to find that *system* programmers
working at the lowest levels of OS design are often almost completly
ignorant of the *machine* implications of the code that they write.
And this is a problem that's only getting worse. You can only fit so
much teaching into a 3-4 year degree, so many core concepts are being
removed from curriculums as it's considered important for a graduate to
have current skills.
When I studied nuclear physics, my first graduate course was a machine
shop course. I had to learn to operate all the machines to construct
parts I might need in the middle of the night when experimental
apparatus (inevitably) fails.
Similarly, no computer science graduate should be able to escape
school without learning how computers really work, and what code
is actually generated by their compiler(s).
This is an excellent preface to a strong course in performance
measurement and analysis. How can anyone be responsible for writing
well performing code if they don't even know what factors in their
code affect performance?
Sure, I realize that 90% of the time is spent in less than 10% of the
code, but one must learn how to identify that 10% and apply appropriate
techniques to improve it to achieve performance goals.
There is *no* substitute for a low-level understanding of what is
actually going on in a system. Unfortunately, higher level tools tend
to further obscure actual system behavior by making it more "esoteric".
I agree. I feel it's a problem we'll never really solve, only mitigate.
higher level tools certainly do eliminate the need for a lot of
low-level understanding, but as your example illustrates, it's when the
problems arise that low-level understanding is necessary.
One has to ask though what an inexperienced developer was doing writing
process scheduler code!
He wasn't inexperienced! He had been doing OS code for several years,
after a brilliant career in the field. But he fell under the spell
of "abstraction for the sake of abstraction", and began designing as
if an OS were algebra instead of a powerful, finely tuned, throbbing
engine that could be thrown out of balance by inattention to the
realities of its execution.
Been there too. Time has proven it doesn't work well, and that test
driven development techniques provide more safety, and allow more
flexible forms of expression in the process, easing chafing.
No argument here--test scaffolds have proven their worth many times
over, yet their adoption as a standard part of any development has
been widely rejected. Maybe we're past that now...
A couple of fairly important tools, Ant (Essentially a portable "make"
on steroids) and JUnit (A Unit testing framework) have become massively
popular in the Java development arena, along with the phrase
"continuous integration", which more or less implies having a fully
automated build/test/deploy framework, to the extent that a machine on
the network checks out the code repository with each commit, builds it,
runs the entire test harness, and emails the person responsible for
violating a test case, should that occur.
The notion of "instant regression testing" after each change is a
powerful and fundamental idea. I'm glad to hear that it's catching
on!
I'd like to see performance testing integrated into this scaffold,
and that includes measuring the impact of nonlocality in distributed
systems.
It's crazy to think that a complex system can be implemented first
and then have performance hacked into it during system test. One
must design for performance, and every module should be designed and
tested to conform to a performance and resource quota that is allocated
as a part of its specifications. This would allow useful performance
modelling long before a system is fully realized.
The difference that this methodology has made to system quality (not
necessarily code quality) and development time is difficult to describe
in words.
One of the nicer side effects is it tends to free developers from the
'fear' that they might break something if they decide to make
relatively major change, since they can easily validate the system.
This has given rise to a practice known as refactoring, which good
developers did anyway, but now has been formalised to the extent that a
much larger segment of the developer community have embraced it.
A critical point, since so many such decisions are based on irrational
fear--often on the part of managers. ;-(
The truth is that very little code needs to be really good. If it's
just 2% of all the code written, then it could all be done by 98th
percentile people. And, frankly, I don't really care whether the other
98% of code is written well, or even written at all.
I agree for the most part, but there's still a lot of code out there I
consider essential that's utter crap. Web Browsers are a good example.
Well, it's about time for three "wizards" to put together the ultimate
web browser. If it takes more than three, then they don't understand
the problem well enough.
It reminds me of "Sturgeon's Law". When, at a cocktail party, Theodore
Sturgeon was asked what he did, he replied, "I write science fiction."
The response was, "Science fiction! 90% of science fiction is crap!"
To which Sturgeon replied, "Sir, 90% of *everything* is crap."
I'm a fan of this law too :-)
So there is no real shortage of good designers, just a poor system of
allocating them to projects. Almost 90% of them are working on crap!
Michael, I hope you're right :-)
I do too. ;-) I know there's a tendency for great designers to migrate
to great projects, but there are many impediments as well, and lots of
entropy stirring things up...(ignorant managers come to mind...
BTW, there are 98th percentile managers, too, but they are, of course,
scarce. The fundamental problem is that a bad manager's scope extends
his influence over 10-100 people, which includes approximately 2 great
designers--and he can thus completely neutralize their abilities to
improve a design.
It is a frequently stated goal of language designers to make bad things
harder and good things easier. What has never been studied is whether
these tools actually improve the productivity and quality of output of
the 2% of *great* programmers, on whom we all ultimately depend.
Most programmer productivity (not to be confused with quality, but not
unrelated) studies have shown *much* greater variance (50:1!) is
attributed to individuals rather than to the tools they use. My own
experience supports this finding. (BTW, "programmer productivity"
has nothing to do with "lines of code" but with "problems solved".)
I have but my own experience to go on, but I've observed in my own work
that using more sophisticated tools has made me more productive.
There's solutions I can build in days using something like Java that
would takes weeks in C. Would the resultant C system be any better? I'd
have to say no, not likely.
Perhaps this puts me in the category of not being a good developer, but
if that's the case so be it ;-)
One cannot judge that without seeing a whole track record. ;-)
But I have known designers who spent 95% of their schedule just thinking
and playing with little routines, then, in the last week or two, sitting
at a keyboard completely "I/O bound", cranking out beautiful code--some
of it in assembly language!
The more common case is 50-50, but a common characteristic is that
apparently non-productive, playful phase, when most of the good stuff
happens.
(BTW, this makes poor managers *crazy*, and can really hurt the great
designer's next evaluation!)
I can appreciate an editor that pops up the full procedure interface
for me as soon as I type a half-dozen characters of its name, but I
note that it facilitates calling the *wrong* procedure as well as the
right one. I'm glad that it lets *me* choose. ;-)
Ironcally, now that I'm using Ruby more and Java less, I'm finding
myself using a good old fashioned text editor (the language is too
dynamic to allow a lot of autocompletion). I actually don't miss the
fancy IDE all that much.
Yep--sounds right.
Actually, I was talking about the 8MB of code--most of it *not* within
the Hellow World application--that will be executed in the act of
painting "Hello, World!" on the screen. The 8MB of libraries embedded
in a trivial application are a separate issue (to which there are well
known but seldom applied solutions).
I think this is a very overstated point - I can't really comment on
exactly how much code get's executed in order for a Java program to
push Hello World out stdout, but I'm pretty sure it's a lot less than
8mb ;-)
Take a look at some hardware instruction counters. The amount of code
executed (most of it in initialization, of course) to load a program,
create a window (with its dozens of widgets), open it, and display a
text object, is mind numbing. (Remember, we're talking about machines
that can execute, conservatively, 5 million operations per millisecond.)
It's not ideal, but what's the alternative? If you don't follow this
road, a cap is placed on the possible solutions you can build. The
overheads introduced by high level abstraction systems is a very
interesting field of research, and one that great inroads into managing
has been made.
I have no problem with the upper two or three "layers" being rather
loosely bound--after all they are, or should be, in flux.
My issue is with the dozen levels beneath these, which have not changed
for a decade, and are much in need of collapsing into a much more
efficient and, yes, less portable structure. (BTW, have you noticed
the steadily decreasing value of architectural portability? ;-)
After 5 years of Java development I've not needed to worry :-) Besides
OS portability is very important these days, and while were only really
dealing with 2 or 3 machine architectures now, it's still an issue.
A good example is Linux, which needs no 'collapsing'. I'm fond of the
KISS principle when it comes to OS design though.
Code which is executed many times per day by tens of millions of people
can be justifiably optimized, since it will save *huge* amounts of human
and machine resources to do so.
Can somebody send this to the developers of Microsoft Office 2003 ? :-)
I can see a time when massive parallel computing clusters 'churn'
through algorithms, fitting them to particular machines are problem
domains. Programming done by the human will be not much more than
assembling from vast libraries of prevalidated solutions. It doesn't
take much thinking into the future to imagine a time when software
complexity is so high that this is the only feasible solution to
building more complex systems.
I remain radically suspicious of any approach that minimizes the
fundamental role of conscious design--unless we plan to use genetic
algorithms to evolve programs. (I'm not entirely kidding here, but
this is clearly not "around the corner".)
My point is that the tools *should* allow the humans to focus on
conscious design, spending less effort on fiddly details that should be
inconsequential today. This is one of the big issues with current
languages, to lesser and greater extents depending on which one we
choose to discuss.
In this area, the fundamental limiters I see are complexity (human
limiter), resources (human and machine limiters), and performance
(machine limiter).
I expect tools to help manage complexity, as you do, but I also
require tools to manage human and machine resources, and machine
performance. In the (related) areas of resources and performance,
tools can be used to help visualize usage so that a designer can
make better informed tradeoffs.
I am quite disappointed with the relatively poor quality of tools
to correlate system design choiced (like class structures) with
real system metrics, like memory resources and performance. As
we move into more concurrent systems, measures of locality will
be extremely important, and remain largely unaddressed in the
current generation of tools.
I think for the most part we agree on these issues - the tools you
invisage are much the same as the ones I do. Going a step further, once
we have tools that can gather such metrics, it's likely that the
techniques people will use to examine them and then 'tune' will be
disciplined enough that a large degree of automation will be possible.
I guess I don't see anything automatically noticing that creating
and destroying timers at process dispatch time could and should be
replaced by an interrupt-time countdown and timeout approach.
Optimization techniques are good at little things, but terrible at
conceptual changes. (Come to think of it, most *people* aren't very
good at conceptual changes, either. ;-)
In the not too distant future, we will be able to treat today's large,
"nonportable" bodies of code, together with the behavioral spec of the
systems they run on, as a "source language" from which we will be able
to compile a program with equivalent behavior on any system we choose.
I agree that this will happen too, but it's far enough away that we
have to make do with intermediate solutions in the mean time. Many,
many years of compiler research stands in the way of such things.
Meanwhile people are still arguing that compiler research done in the
1960's and 70's, such as using p-code machines as intermediate forms
along the path to better optimised solutions are a bad idea! Too
inefficient they say. For the most part, this is baloney. I tire of
issues that have been solved *years* ago continually being touted as
reasons to not move forward.
Not really that hard. Binary translation from one architecture to
another has been done well for over a decade. NASA and the phone
companies are relying on it to replace failing old machines.
Interpretive approaches have their uses, but they must be augmented
with native execution where it matters--but we agree on this.
Imagine how much smaller system footprints could be if all the error
handling code were kept in bytecode (since it's executed very seldom)...
The real problem with legacy applications is that we need them to
continue to behave *exactly* the same, to avoid compatibility ripples.
The best way forward has always been not to "port" legacy apps, but to
*replace* them entirely with new subsystems, supporting new functions.
Indeed! Which is why I advocate moving to newer systems, like VMs! It's
the 'newer' systems that encourage reuse of legacy subsystems that
should be thrown in the trash that I dislike.
The practical useful lifetime for code is measured in the cost of
human change, not machine change, so having practical portablity
may turn out to obstruct rather than expedite the move to a truly
better solution.
Hmm. A decade of industry success to Java indicates that this is
probably not the case.
I wasn't referring to Java, but to other techniques to port legacy
apps.
I'm reminded of the early 1990's when John Carmack announced that DOOM
would be written completely in C using cross compilers (they used NeXT
workstations). He was scoffed at by industry types at the time that
believed you couldn't write a game that pushed PC hardware to it's
absolute limits without hand-tuned assembly routines in the critical
sections. DOOM came along, and a lot of people ate humble pie.
More recently, John announced that ID's next 3D engine would probably
be written in Java. Again he is scoffed. I'm personally looking forward
to the pie eating that will most likely ensue :-)
Yes, I remember when the "common knowledge" was that an OS could not be
written in a higher level language. Four years earlier, Burroughs had
already written their MCP OS in an Algol dialect with a few system
extensions. It was like listening to arguments about how a bumblebee
can't fly!
I guess I'm willing to bring what has become a private conversation
to a close. I see that you are both earnest and idealistic, and hope
to accomplish great things within the frameworks you have chosen.
More power to you!
I would only urge you to periodically re-evaluate the tools landscape,
looking particularly for those that shed light on areas hidden by the
tools you use most. This will keep you in touch with what is *real*
as opposed to what is merely linguistic, and that is essential in the
long run. Perhaps you will design the language that makes it easy for
designers to map algorithms onto colossal distributed systems spanning
eight orders of magnitude in bandwidth and latency!
-michael
Parallel computing for 8-bit Apple II's!
Home page: http://members.aol.com/MJMahon/
"The wastebasket is our most important design
tool--and it is seriously underused."