Show HN: I made an easy-to-understand 6502 emulator in pure C
github.com
github.com
I'm still not sure how I feel about it -- you could argue that you have the same problem, but now it's spread out over many files. At the same time, I can look at the loop and have a pretty good idea of what it does and I can easily find what I'm looking for. I guess without OO, there isn't a great solution to the problem, although yours is probably a slight improvement, with the added bonus of getting me to think about the preprocessor in a new way.
Good work.
I know that there are definitely cases in which an OO approach beats a bunch of switch statements scattered throughout the code (specifically, when checking the type of the input variable), but I don't think that applies here. If I saw an interpreter written in that way I would assume it was created by someone who didn't know what they were doing.
As an aside, 1) I can't stand the term "code smell" as it tends to be used by hipsters with little to no experience building complex systems, and 2) I realize that you posted your honest thoughts and did a good job of analyzing the approach taken by the author. I'm not trying to prop up a straw man here.
To get a sense of its history, https://books.google.com/ngrams/graph?content=code+smell&yea... put a start date of around 1998. It was definitely in Fowler (1999) "Refactoring: Improving the Design of Existing Code".
See also http://c2.com/cgi/wiki?CodeSmell , which claims "A code smell is a hint that something has gone wrong somewhere in your code. ... KentBeck ... seems to have coined the phrase in the "OnceAndOnlyOnce" page" and points to "Refactoring" as earliest known use.
The phrase you refer to - "this code smells" - and the implication that it reflects hygiene, has different meaning and intent. While related, they are not the same thing as a "code "smell." Compare "the wine smells" vs. "wine smell" to get a sense of how there can be a difference.
The modern use of the term "hipster" dates from 1999-2003, claims Wikipedia at http://en.wikipedia.org/wiki/Hipster_%28contemporary_subcult... , but it's easy to find things like the 1998 alt-comic "Urban Hipster #1" at http://www.indyworld.com/uh/uh01.html which predate 1999 and use hipster in its modern meaning.
In any case, the term 'hipster' is derived from a term in widespread use from the 40s-60s, which then fell into disfavor. To get a rough idea of the change in use pattern, see https://books.google.com/ngrams/graph?content=hipster&year_s... .
Obviously the 1940s "hipster" term predates "code smell" as applied to computer code, because there was no computer code in the 1940s.
So no, "code smell" does not predate the term "hipster."
I agree with you that "code smell" did not originate from hipster use.
But I don't think EpicEng implied that either. The parent comment is "it tends to be used by hipsters with little to no experience building complex systems." I don't see in it an implication that hipsters came up with the term.
So if that is fit2rule's point, then it's rather like saying "fixed-wheel bicycles existed before hipsters" - true, but a tangential remark which ended up derailing the conversation.
I know of no use of the phrase "code smell" before 1999, with its modern use. Could you speak more about your recollections from the 1980s? Was it used the same? Do you have a citation or reference?
Who knew I would get such an education on the term "code smell"? :)
And if you're writing a program that does one of hundreds of things based on the value of a byte, the options for really clean code are limited, and you can do a lot worse than a huge switch statement.
Here is an interesting article on the topic:
But people do use them an awful lot for watching movies too...
Most optimizing compilers will emit a jump table for a switch, which is a good way to get a reasonably high performance interpreter while keeping the code pretty clean. The performance is better than using virtual methods [1].
For example, look at Python's ceval.c, which has a switch table implementation and the text "The traditional bytecode evaluation loop uses a "switch" statement, which decent compilers will optimize as a single indirect branch instruction combined with a lookup table of jump addresses."
Actually, it also has a version which uses GCC's "Labels as Values" extension, see http://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html . The comment then points out that this non-portable solution is up to 15-20% faster than a large switch statement, because having N jump points instead of 1 improves the CPU's branch prediction.
As haberman says, this is "actually good code", not "justifiable" bad code.
I am another who dislikes the term "code smell." I agree with mbrock - I would rather get a comment which reflects the underlying complaint than use a proxy term like "code smell". In your reply to mbrock you wrote:
> the "smell" is just a heuristic that says that without any additional arguments in its favor, a hundred-case switch usually isn't the cleanest and most maintainable way to implement something.
Why not just say "a hundred-case switch statement usually isn't ...", and omit a reference to "smell"? What additional meaning does the term "code smell" lend?
I dislike using the term "code smell" in general. Some people detest stinky cheeses, or a peaty whisky, while others adore the complex aromas. Often this is a learned taste which comes with age and experience.
As a result, the obvious rebuke to any "this is a code smell" comment is "that's because you aren't mature enough to appreciate it." I can't think of any effective response which stays on topic, other than to bring the conversation back to the specific problems in the code. Why not just start from that point instead?
For example, I wrote a very small JIT compiler for Brainfuck. It uses switch() to do code generation. It's extremely clean and elegant code, if I do say so myself. Breaking apart the switch() with some kind of OO approach would make the program longer and less clear: http://blog.reverberate.org/2012/12/hello-jit-world-joy-of-s...
Do you look at that and think "this doesn't look good" just because it uses switch()? If so, your heuristic could use a little tuning.
But now let's talk performance. In interpreter/VM main loops, bytecode dispatch is a huge factor in the overall performance of a VM. This issue isn't "theoretical," "minor," or "premature," it's the result of extensive measurement and experimentation by many people over a large amount of time. If you are not familiar with this well-studied issue, here is some recommended reading:
http://software.intel.com/en-us/forums/topic/298506
http://lua-users.org/lists/lua-l/2011-02/msg00742.html
http://www.cs.tcd.ie/publications/tech-reports/reports.07/TC... (see section 3.3)
It is not a bad pattern to have switch in those cases, but it is often present in code analyse rules. Code analyse tools are very bad to distinguish between good use of a feature that is rarely used and missuse of this feature.
If you create code that smells, you should justify it (in comments or other documentation). Likewise just saying it smells without suggesting an alternative is unhelpful to the discussion.
Can you contextualise the criteria? Can you match those criteria to this context?
(Not rhetorical, I'd like to know the answer.)
https://code.google.com/p/oriculator/source/browse/trunk/650...
Which do you find easier to read/understand/work on? In my case, I still think the #include'd switch case's to be less readable in comparison .. although it must be admitted that oriculator uses the C macro processor in its own nefarious ways .. "READ_ZIX;" indeed .. ;)
(BTW, oriculator is a very mature 6502 emulator .. and also rocks as a way to return to the glory days of the 80's machines that never got enough love: the Oric-1 and Atmos...)
That said, I find it much more difficult to read. The reliance on long, complex macros means that I need to flip back and forth between the macro definitions and the opcode interpreter, and long, multi-line macros like they have give me the heebie-jeebies. I'd rather see inline functions used (like x6502 does in functions.h; the functions in functions.h serve much the same purpose as the macros in oriculator).
Not to take anything away from the project -- it's obviously an amazing software project and an incredible achievement. We just set out with different goals in mind: mine was more pedagogical, and theirs was more practical.
http://prog21.dadgum.com/166.html
"The possibilities when compiling a switch are much more varied. It can result in a trivial series of if..else statements. It can result in a binary search. Or, if the values are consecutive, a jump table. Or for a complex sequence, some combination of these techniques. If each case simply assigns a different value to the same variable, then it can be implemented as a range check and array lookup. The overall sweep of the solutions, from hundreds of sequential, mispredicted comparisons to a single memory read, is substantial."
var ops = new Dictionary<Opcode, Action<opcodeArgs>> ()
{
{0, nop },
{1, add },
...
}
or even better, maybe I'd get a list of the opcodes with their names, name all the functions after those names exactly, then use reflection to automatically create this dictionary.Then in the main loop,
ops[opcode].Invoke(args);
I don't know if something like that would be any use in C, though.For example, there are measurable differences in branch prediction based on whether each opcode's implementation ends with its own dispatch code or branches back to a common dispatch routine. The sequence of instructions is basically the same in both cases, but there is an icache/branch prediction tradeoff to be made. Subtle stuff (see http://repo.or.cz/w/luajit-2.0.git/blob/0ded8e82a88fadb40b4d...)
I often hear this from programmers coming from a higher-level background. O(1) only means constant time. It doesn't mean quickly. This function:
int foo(int x) {
sleep(3600);
return x;
}
is O(1), just as this: int bar(int x) {
return x;
}
Foo takes an hour to commplete, but it's just as O(1) as bar :-).switch is O(1) and fast as the operation is usually 2-3 deterministic time instructions:
1. Look up jump address from lookup table made by compiler.
2. Jump to it.
More people need to have written assembly to actually get this into their heads.
jmp [lookupTable + eax]In general, if I need something to avoid function calls, I'll use macros as a shorthand for a complex expression (like NEXT_BYTE) and inline functions for anything that involves one or more full statements (like set_flags). Obviously I don't follow that too dogmatically, because mem_abs is an inline function.
So when I read the author's code and saw the way he handled a similar problem, I was intrigued. If you had to deal with a massive switch statement with thousands of cases, it would probably be easier to maintain IMHO.
For a hobby project, the author's solution is 100% correct and I'm not critiquing it at all. In fact, the only reason I even commented was because I thought his solution was better than any other procedural solution I had seen before.
With that said, in an enterprise environment, where maintainability is crucial, I'd argue that a massive switch statement is probably a bad idea. Going back to messloop.c, what happens if the user tries to change a floating point value through the UI? Well, I can tell you that there is a case statement for that somewhere in messloop.c. What is that case statement called? I'm not sure, all I know is it's a #define that I'm sure made sense to the original author. It's basically a needle-in-a-haystack problem.
However, a lot of desktop applications are written that way. If you've ever dealt with Win32, you'll see nested switch statements from hell on your average project.
If you have a pure OO language like Java or C# then there's no excuse but some legacy applications built in C tend to be "switchy" because the older APIs seem to favour that form of message dispatch.
There is still no excuse as you can have decent abstraction in C or C++.
However for what is effectively a jump table, a switch statement is exactly spot on for this project.
* you went easy on #define's, they obfuscate the code when used heavily
* you have a simple switch instead of an opcode table with function pointers or something like that (may seem more fun to write, harder to read for someone inexperienced)
* light on comments, which is also excellent, the code is self-documenting almost everywhere.
* the unusual includes for opcode categories are OK. If you wanted it to be more straight-laced C, could put them in separate .c files and break the switch into many switches, one per file. It's probably better the way it is.
One thing it'd be nice to add is a short guide, right there in README, in which order to read the source (e.g. start from cpu.h/cc to understand how the state of the CPU and memory is represented, then cpu.h/cc for the main loop executed once per instruction, etc.).
The interesting thing is that I wrote very little of the code by hand; instead I wrote a program that generates the opcode implementations from a concise specification. The end result is remarkably compact, and the whole thing was really fun to make.
Did you use this for generating the opcodes? The Z80 ISA encoding is quite regular and octal-based: http://www.z80.info/decoding.htm
As for the improvement, I never liked switch, for unknown reasons :) You mean an improvement in style, performance or both?
IMHO the switch would improve both style and performance, what caught my eye when I looked over the code was that different flags would require different amounts of time to execute, which really seems an odd thing for an emulator to do.
http://simulationcorner.net/index.php?page=c64
Only 30KB of code and it runs a C64 on it.
We sent the test team who built it a big "you're awesome" card.
He suggested to take HEX as the input and I never looked back. I see this emulator just reads the bytes from the binary.
Does anyone know if the GCC AVR toolchain spits out a file I could do the same thing with?
* It's way cooler if you can use other peoples' assemblers and still have your bytecode interpreter work
* Using other peoples' assemblers is a good way to sanity check any assumptions you made (this bit me with branches, for example; I was branching from the location of the current instruction when the spec is to branch from the location of the next instruction)
* It's easier to read binary than to read hex strings and transform them to binary.
Plus, transforming binaries to readable hex is as easy as "hexdump -c". :)
How about inline functions or macros?
xa65 - cross-assembler and utility suite for 65xx/65816 processors
This package is available in the Mac homebrew repository, so maybe try that?