CppCon 2016: Jason Turner “Rich Code for Tiny Computers: A Simple Commodore 64 Game in C++17”
Вставка
- Опубліковано 22 сер 2024
- CppCon.org
-
Presentation Slides, PDFs, Source Code and other presenter materials are available at: github.com/cpp...
-
The Commodore 64 was released in 1982 and is the best selling computer model of all time. At 34 years old, even the most simple embedded processor today outperforms it. Join me on an exploration of how C++17 techniques can be utilized to write expressive, high performance, high level code for simple computers. Together we will create a game for this aging system.
You'll leave the talk with a better understanding of what your compiler is capable of and be able to apply these ideas to create better code on modern systems.
-
Host of C++Weekly / jasonturner-lefticus , Co-host of CppCast cppcast.com, Co-creator and maintainer of the embedded scripting language for C++, ChaiScript chaiscript.com, and author and curator of the forkable coding standards document cppbestpractice.... I'm available for contracting and onsite training.
-
Videos Filmed & Edited by Bash Films: www.BashFilms.com
*-----*
Register Now For CppCon 2022: cppcon.org/reg...
*-----*
Who else came to watch after hearing Bjarne reference this talk on Lex Fridman's AI podcast?
Depth One DJ yep. Gonna listen to Lex's interview with the creator of clang next.
Me too. I haven't used C/C++ for 20 years. This makes me want to start using C++. Lex's interview with Bjarne Stroustrup (ua-cam.com/video/uTxRF5ag27A/v-deo.html) is very interesting and inspiring.
Here is the reference: ua-cam.com/video/uTxRF5ag27A/v-deo.html
I was actually here before, but then I lost this talk and I found it again trough Lex's podcast
Bwuahahaha!!! ME~!
I know this video is 3 years old, but I keep referring people to this video because it's doing such a great job at dispelling some misconceptions. Unfortunately, there are no closed captions available for this video, which prevents me from showing this video to a deaf co-worker.
I know generating *proper* captions would be a mammoth undertaking, but I would appreciate it if the automatic captions for this video would be enabled. Thank you in advance.
Yes, subtitles are essential for all the non English-native people, too, I cannot understand everything by just listening.
What misconceptions do people have that it is dispelling? That modern languages can't run on older hardware? Or is there something else you're referring to?
i have just directed 32 people to this video :)
About the captions, I've noticed CppCon generally tends to have humans writing them (which you can tell because whole chunks of sentences appear at once, rather than word-by-word). But nowadays UA-cam does have automatic CC generation that is actually fairly good!
So what I think happened is the CppCon channel admins enabled human-written captions on this video, which removed automatic CC, but never actually wrote the captions.
Why don't YOU write the captions? Do you not care about your coworker?
To me this is THE best C++ presentation in the post C++11 era. Have watched it twice. Entertaining, interesting (inspired use of retro-computing as a hook), and informative/educational in respect to C++ the language
Is truly amazing to see the optimizer in action, maybe we can port Unreal to C64 with some constexpr
_Arghh the sarcasm's too bright!! I cannot see the point!! Where is it!!_
Actually, i think with this level of optimizations modern compilers bring to the table - Wolf3d "might" be possible. granted you need to set it to lowest res or even stretch lines twice.
Aw u propably mean Line Interlacing
it is possible, but it will run at 1 frame per year.
@@jomunoz ua-cam.com/video/Pa9cC6HcgrE/v-deo.html
Granted SuperCPU, but still on a C64. :)
"Can we do this with some overhead or no overhead?"
The answers to that question continued to surprise me over and over again! It's one thing to be told to trust the compiler to optimize for you, it's another to see it in action! Thank you very much Jason for this enlightening and encouraging talk!
I did not understand what he meant by overhead in this context. Can you explain?
@@marcusterrel1686 When you call a method, create an object or even call a function normally without optimizations you will have a lot of overhead (meaning added instruction in the assembly code). But the compiler is able to understand that you don't really need methods or objects in some case and remove them for you. The more known example to remove overhead is the inline keyword.
A lot of languages can seem to drift toward use in producing ever more bloated software as the language sophistication and their communities mature and evolve over time. And yet C++ is getting better abstractions for making the chore of programming easier while seemingly doing a better and better job of generating efficient software. This talk was an eye opener in that respect. (And Rob Irving's and Jason Turner's C++ podcast surely deserves a plug - kudos!)
Honestly, I'm not so sure about that. Don't forget that Jason is literally earning a living by knowing C++ inside out, so of course he knows all the cool tricks. But that's like watching Tony Hawk and deducing that skating is easy because you only need a few quick feet movements. Actually learning the language is getting harder and harder since all the cool new features (which are genuinely improvements!) are nonetheless based on the old systems. So now you need a good understanding of both and so on
@@TheMrKeksLp I think by easier, he meant for those who do know it inside out because they can use the language more thoroughly and not take extra time to write workarounds for things that should be built-in because they are built-in.
The optimizer isn't going to do as good a job when things get complex enough. If it's written well, it should still be faster than many languages, but don't expect to have more lines of C++ code than of assembly like happened here in several versions of the code that often.
28:00 that was SUCH a badass moment haha
Just * mic-drop * :D
Or actually, more like "Cool guys don't look at explosions"-moment ^^
haha indeed, I wanted to show this particular moment to a friend of mine and I found it thanks to your comment! thanks
Amazing talk! You can show this presentation to C programmers and convert them to C++. It's hard to convince them without showing 0 overhead. :)
C64 modern programmers are so much better than this (in terms of effciency) it hurts. Check any recent demo from Oxyron, for an example.
This video of c++ on c64 is entertaining, but says nothing about optimization.
HL programming languages on c64 are nothing new, there existed several alternatives in the 80s, and these were not used for commercial projects (with some exceptions). You could do little things like this Pong game, but not really compete with the asm games of the time.
There is a couple of things to note about this. First, the point of this wasn't to write an extremely efficient c64 game, it was basically to show that a lot of the "C++ is bloated and can't write for small devices" is really not true.
Secondly, about the efficiency, all I can say is isn't that obvious? The binary output is from an x86 compiler and then translated to 6502. These are vastly different platforms so the compiler would optimise in different ways. If the compiler he used was specifically targeting the 6502, then it would be a lot more efficient, but as he stated right at the start, adding a target to a compiler toolchain is hard.
@Darran: You got it right. You can show this video to the people who says that C++ compilers are bloated or less efficient than C. Theoretically, there can't be a performance difference for the same logic in both languages. I guess we are reaching there in practice now.
@Luis: I'd seen that demo. Astonishing! As Darran said generating efficient c64 code is not the point of the presentation.
After watching this talk to me it seems that it takes a lot of knowledge, discipline and verification of assembly get 0 overhead. Where in C it is 0 overhead by default, any overhead is explicit.
That is the problem with modern c++, too complex. But if you want top-notch abstractions, high level language features and manageable code without sacrificing performance then you don't have a choice.
Absolute madness. I'm amazed every time I dig into the low-level aspects of C++.
Some of the question askers were twits though. "Hey did you try this? Do you know about this? I know about this! I KNOW THINGS! I'M THE BEST!"
Reducing compile time is a valid optimization
Excellent presentation, It was like seeing magic how compiler can optimize code .
Funnily enough, now they're selling modern very flat keyboards with Raspberry Pi inside, so in a sense you can get a super computer version of Commodore 64. Just a lot thinner and faster.
I guess this talk is a great demonstration of why Rust having immutable as default is so great. Before starting to learn Rust, I never paid that much attention to making things const in C++, only some things that were like default values, something like PI. But I started passing const references a lot more after touching Rust.
It's hard to name all the things this talk demonstrates about C++ (but obviously the summary of zero cost abstraction was huge), but it's pretty great in showing all kinds of parts about programming in C++. I don't want to see smart stuff if this is the stupidest thing to do, this is what I'm up for.
the actual correct use of volatile !!
27:15 im going to show this to anyone who complains that I overuse const
Overuse const? That should be default. I use "val" (#define val auto const) almost everywhere.
Answer: “Oh snap, sorry, you needed to change that at runtime?” Then, with an innocent, interested expression, let them dig themselves a hole.
22:00 Every copy of Commodore 64 is personalized
Did something similar in a small hobby project of defining a gameboy CPU dispatch table as a set of traits, with each opcode handler generated from a combination of inlined functions which end up operating directly on the memory representing the emulated CPU state. Compilers are smart.
I just started to learn C++ and the video was amazing. Thanks.
It would have been a nice inside joke if instead of the memory() function you would have used a poke(uint16_t address, uint8_t value) function ;-)
Cool!!! (And I wrote quite a bit of assembler for the 6502, back in the late 70s for the Apple IIe!!!)
Awesome talk!
Would be interesting to see some of the homebrewers getting inspired by this
28:00 it's truly 👀 opening
Crazy good. I appreciate seeing the possible optimizations and some of the new language features I need to pick up on. Good talk!
Extremely good presentation, near perfection
Compiler is amazing, but I hope people realize that there are a lot of architecture specific (C2D, Skylake etc.) and larger scale optimizations (ragarding how the cpu:s internals work and how ram is accessed etc.) that compiler can't really do. I've seen enough code where people trust too much on the compiler and are filling the CPU-pipeline inefficiently or accessing ram in a way that's suboptimal.
I loved the talk though to show how good the compiler really is as long as you have some clue as to what you're doing to make it optimizable by the compiler. :)
still interesting 6 years later :) I populated chips on the cards for the hardware atari in 1976 while in college for a semester when I couldn't get into school on a deadlines issue....
cc65. A modern C compiler for 8 bit machines including the 64
C, not C++. Huge difference. Almost none of what's been shown by Jason in this talk is possible in C.
6502 compatible CPUs were the ARM CPUs of the late 70s/early 80s
speed = 1.5 is good for this one. Nice talk! Sorry about the technical problems at the beginning.
I love this talk
Super interesting and impressive demo of C++ possibilities !
@27:33 I'm happy being able answering good to the question how to reduce code footprint of Color tables generated code: I would try avoid all compile-time possible calculations using const, constexpr and perhaps reorganize data structures to take this optimzation further.
Does anyone have any ideas about the typical memory footprint of a "hand rolled, contemporary with the hardware" version of pong?
A quick search is telling me between 1.2k and 1.8k bytes for the ZIPPED ROM. Assuming compression removes 25%, that means the handwritten games use between 1.6k and 2.4k bytes. So ~1.5k here seems to be one of the more compact variants of the game.
Had a look around, best I could find:
Pong by "Tomi Malinen" was 2 blocks, 512 bytes. Its single player and black and white.
Its the smallest I could find on some random website I've never seen www.gb64.com/.
Thank you Jason. Great talk, very entertaining and informative at the same time.
Source code:
github.com/lefticus/presentations/tree/CppCon2016/RichCodeForTinyMachines
Excellent demonstration and a great presentation. Thank you very much.
This is why i love C++.
Haha, this is why i don't like C++. I found the final C++ version almost unreadable
I will start using const everywhere;)
Is there a mistake between minute 37-41 in checking ```while(vic.background() != 240)``` when background() returns the memory reference of background color. Instead it ought to check for ```(vic.rasterline() != 240)``` where rasterline uses the memory address of SCREEN_RASTER_LINE. This problem was corrected in minute 42 in the "sanity check" which uses the correct memory address @CppCon
Nice workshop!
As a Commodore 64 and C++ programmer, I have only these questions:
1. Show me the C++ source file.
2. Show me the 6510 executable.
Source files: github.com/lefticus/presentations/tree/CppCon2016/RichCodeForTinyMachines/Source
Cool. This really gives me some insight into how compilers work!
I LOVE C64 👍🥂🎩
Its been a really long time since I did any c++ and I dont know anything about the commodore 64 architecture but I was really confused that he was doing "vic.background() != 240".. isnt this supposed to be "vic.raster_line() != 240" and he needs to define a raster_line function to return a pointer to that memory address? Maybe i am missing something here, but i was sitting here trying to figure out how that could possibly work because it made no sense to me so I assumed it was my poor knowledge of c++
That confused me too. Some research suggests the address for the current scan line is $D012, so I imagine it was just an oversight/typo.
Exceptions in C++ seems a game breaker regarding the zero-overhead thing. You have to be very careful not to use any thing that might throw.
Quite impressive talk, BTW
Stroustrup has said that he believes people overemphasize the exception situation as bad. Yes, exception-safe code has some extra instructions in the binary (quite a lot actually), but unless your program is constantly throwing exceptions, expect those code paths pretty much never to be used. You only have to obsess over removing exceptions if the size of your binary matters. The result should run plenty fast, just a tad slower.
28:06 was amazing :)
Rust got it right, everything is immutable by default.
Awesome talk!
Anyone else, miss how quickly the C64 booted?
no, because that didn't matter at all as you had to wait ages for any program to load from tape
Oh, right, or type in programs from magazines :)
no because my PC starts in like 4s and loads programs in under 1s
Oh, but when I first go my C64 I had to load and store software to a cassette tape - loading software from that was not a very quick process. (Later got a floppy drive but it was not particularly very fast either, but better than tape.) Cartridges were for games and game companies. Yeah, you could turn it on and get a near instant prompt to start twiddling in BASIC, but so what?
Oh good point, I had forgotten about that. You reminded me of the pains of the olden days.
LMAO 27:58 magic!
15:00 wait - a function in an anon namespace is visible in `main`?
24:30 wait - template functions can return `auto`?
39:00 wait - you can declare structs within functions?
59:00 wait - you can do… you can do whatever the heck that is?
@@RoamingAdhocrat Some of these features were pretty. You couldn't do any of these things in C++ 20 years ago. But you can for some time now.
@@RoamingAdhocrat por supuesto, and for ((int) i=0 , i
Quite amazing.
Well done, this was eye Opening.
Great work Jason
What about just using the C compiler of the Commodore 64
Anyone know how that const was working like that, Or where good specific info about it is ?
If something is const in C++ it has to be known at compile time, this involves both the "constness" of an object as well as "const storage". It can not be deduced at run time, it has to be known at compile time. By making it const it is initialized when compiled and the compiler optimizes away everything on the stack that fits into the registers.
@@skilz8098 this just isn't true, const just means that the value assigned it won't be modified afterwards.
@@jacobschmidt It is true sometimes depending on context.
Watching guys like Jason talking and coding makes me feel like a total noob)))
Awesome presentation !
Pong is ultra simplistic though, I wonder how well we can get 0 overhead with a more contemporary app
Very cool Jason!
This is brilliant !
Jason 💪💪💪
It seems my computer just rebooted on me. Just a moment ... [I was expecting] I will quickly recompile the kernel.
32:00 "while true if-initializer" That should of course be "while initializer". Why doesn't that exist?
You can use an equivalent "for"-with-initialiser expression.
Very inspiring Talk!
Modern assemblers, even on the C64, are actually not _that_ hard to program. And it is even possible to use PC-based assemblers to create programs _for_ the C64. There is even at least one modern development system for the Atari 2600!
JW3HH
What would be the difficulty in writing an assembler? They are quite straight forward imho. A compiler on the other hand..
I find it quite unfortunate that it is only one file and that it doesn't look like a normal C++ project. Do you think this would work with the usual structure of different source and headers files and probably LTO ?
Is there a point to const pass-by-value arguments to function? IIRC I've seen the Core Guidelines advise against this, but can the compiler use this to do smart optimisations?
I believe it doesn't change anything for optimizations, it just changes the fact that you can not change its value. If you think about it, const or not it is going to be a local variable regardless.
The case of a static array is different, because a static array could be updated at some point, and the program would have to read the new values.
Could this be done to do stuff with a NES using something like NESHLA and a flash cart?
There are ANSI C compilers for 6502 for ages, and a handful of games has been developed for the NES in C. No need in x86 to 6502 translator, and C++17 isn't something you would want on the NES anyway.
This video shows C++17 is something you want on a NES. Why would you want to code in C when the higher level stuff costs nothing?
I'm curious about the choice of using x86 to generate the code. I would expect it to be easier to write a translator from a source architecture like ARM or Thumb
probably not. CISC has more complex instructions and those are simple to break down. It is harder to rearrange incompatible RISC instructions.
Very informative 👍
Glad it was helpful!
best talk so far for me : ) great job. i looked up the online tool. pretty cool. i was expecting MS Visual C++ to show up in the list of compiler... that would be asking too much i guess.
awesome!
FYI it's there now.
I would have loved it if when he needed to reboot his computer, the projector displayed multicolour flashing screens for 20 minutes.
1:05:00 The game
pretty amazing.
Is there any way to avoid generating machine code (one x86 MOV instruction per byte written) for the vic.make_sprite()? That is, have the compiler generate an array of the sprite data (to be placed at the correct address by the linker) instead of generating instructions to initialize the sprite data array?
I tried converting VIC_II::make_sprite() to a constructor VIC_II::Sprite::Sprite() of a struct with a 21-byte member, and using placement new, but also that generated machine code for initializing the 21-byte array. Any better ideas?
I think I found the answer. And it is of course 63 bytes (3 bytes per line, 21 lines).
The objects have to be declared const in the global scope. If they are non-const, they would still be initialized at runtime. For the required 64-byte alignment, I used a GCC extension, which clang appears to support (I am seeing .p2align 6 in the assembler output). The declaration is like this:
struct Sprite {
uint8_t memory[63] __attribute__((aligned(64)));
template
Sprite(D ... d)
{...}
};
and the usage is like this:
const VIC_II::Sprite ball(...);
Note: clang would omit const objects from the output unless they are explicitly referenced. So, for testing purposes, I added fwrite() calls for dumping the sprite data to the main() function, before the infinite loop.
I submitted my solution at github.com/lefticus/x86-to-6502/pull/2
Cpp = crashing powerpoint. :)
Even the most recent versions? They even changed the Visual Studio IDE to .NET for some reason.
Kayak Fan Only the GUI wich uses C#. Same for PowerPoint.
Can anyone explain to some degree what happened when he used the const at the beginning of that function? I can see the assembly code decreased dramatically but not sure what const did to do that.
The std::array that we used initially was mutable so the compiler was generating code assuming that the std::array is mutable. It built the whole array at runtime by generating the Color structure and storing it into an array. Furthermore, when we indexed it to call the `nearest_value` function on the array member, the in-lined code was generated for it by indexing into the runtime built array. So code was being generated to construct, store and index into the array. But we are using it as a read-only structure so we can let the compiler optimize it further by marking the array as read only. When we changed it to a `const` array, the compiler was given a hint that the array is a read-only array instead. The compiler can use this info to build the array but it wouldn't have been different from the code generated for the mutable structure at that stage. But the compiler didn't stop there. It saw that we only indexed a single value from the array from our now read only array. So it went a step further and didn't even bother to build the array. It instead took the value directly and calculated the result of `nearest_value` at compile time and saved that instead. This step removed the code for generating our array, and also indexing into it to calculate the `nearest_value` value. It can do this for a read-only array since it can know that the values of the array won't change. It can just index it at compile time and use the value directly instead. But if we just used the mutable array, it can never be sure that a subsequent call won''t change the values of the array so it's instead forced to generate code to build the initial array, index the array and get the values at runtime which ends up generating a lot more code.
tldr; it didn't even bother to build the read-only array and compiled the resulting value of the function directly instead. That's why the code ended up being small.
46:40 name them d7 through d0 in that order, then! :)
amazing...!
It is certanly interesting to compile existing C/C++ code to old architectures, but the truth is that even C does not suit them very well.
I am not convinced using 'modern C++' actually makes you more productive on those architectures.
BTW it took me half day to get LLVM working in the Z80. What it can do is pretty amazing, but those are really heavy toolchains.
Yeah. For C64 you have KickAssembler which I think is the preferred way to program it these days, if you want the lowest overhead possible.
Awesome
awesome........
makes game in front of us. Asks if we want to see him play it. OFC you goober!! Show us the goods.
I don't quite follow the part about the background, once it is outside the video area the value becomes 240? why?
since no one else has replied, the video chips actually "drew" outside of the viewable area on a screen (overscan), which meant that a frame actually took longer to display than it looked like, that is, if you had a slow motion camera aimed at the screen, there would be a delay from when it drew "the last line at the bottom" and when it started to draw the top line again (ignoring interlacing) - the code he wrote to do the updates only changes the position of everything on the screen when the video chip is currently in the overscan region (horizontal line 240).
Every so often you'll see some video that's been copied from VHS or laserdisc or even DVD and converted without cropping, and it'll have weird garbage going on at the edges of modern monitors/tvs/screens, same reason, overscan - you can capture raster that is out of bounds with modern capture hardware, and unless you crop it in handbrake or ffmpeg or whatever, the artifacts will be there. Technology Connections (or connextras) has some info about how the overscan areas were used during the era of analog broadcast around the time that color televisions hit the scenes.
A shame you dont show the C64 emulator running the code as well while you do the coding/testing.
It wasn't an emulator. He had an actual C64.
Stupid? No, why would it be stupid. Doing something you enjoy, fancy, and take fun and pleasure in isn't stupid. Creation is never stupid.
As for PowerPoint crashing: I must be the only person in the whole world who RARELY experience issues with Windows and Office...What are you all doing to your computer, ladies and gents!?
what is "overhead"?
How did you manage to translate the x86 asm code to 6502
cross compiler
A cross-compiler translates all x86 instructions to (usually) one or more 6502 instructions
6502 is a brain of f14 tomcat jet fighter
Please, edit the title. It says "Commodor" instead of "Commodore".
they did it :)
this is amazing, is there a similar compiler explorer for java?
For Java, you have the output byte code right there for viewing. You can also run a reverse compiler on it to see what the code looks like after compilation. There might be simplifications like fewer intermediary variables or entire chunks of code reordered. But Java isn't really known for the highest performance, which makes looking into making highly tuned Java code make less sense. When you write something in Java, you are already admitting it's all right if it's about 2-4 times slower than the fastest option, and you make that choice to have an easier language to use that you can make meaningful software with faster and hopefully with fewer bugs. Simply put, all the bells and whistles in C++ make it a harder language to use than most popular languages.
where can we find the code for the translator?
bookmark: 25:28
why's is he adding the auto keyword if all of the functions and classes default to auto?
For anyone who came here for const moment: 27:59
Some overhead? Bueller ? Bueller? No overhead? .....
This guy is not a very good presenter, but it's a pretty solid presentation non the less.
He's a very good presenter imo. Very few people present while doing live coding and demoing on a 40 year old computer. Lots of things are going though his mind most likely, also he got struck with the curse of the demos, where his computer crashed right as the presentation started making his keyboard shortcuts malfunction. He handled it pretty well imo.
Seriously?
x86_64 have 16 registers and it would be impossible to use all of them on PC (it doesn't matter if it is Windows or Linux or something else, as it is physical architecture which eats up resources, not logical one
i386 had 8, of which 6 were (sort of) general purpose
this is NEITHER PowerPC (32) NOR SPARC (32 with banks or renaming (I am confused now, as I do not remember) plus each thread have full set of registers)
I think he's counting the sub-registers separately... so maybe counting AH, AL, and AX as three registers
x86_64 has like 40 registers, if you count SSE registers and the like. Lookup x86_64 register map.
at least at the beginning of the talk, the cameraman must've been so annoyed at you.
I want to be a master of C++.
How long does it take?
Until you’re done.
That will be awhile after C++ stops evolving, well after everyone has mastered all its features, or so they think.
C++, for better and worse, is perhaps a modern replacement for COBOL but less portable between implementations by its very nature, by design.
C++ templates are themselves Turing-complete, and people have used them to have the C++ compiler generate an answer by “running” the template program.
It is these sharp edges that will cut you the most, as C++ has a focus on performance, close-to-the-metal, and that means a huge number of things are specified as “implementation left to the vendor” and non-portable.
All your life, most bloated language ever
This is the dumbest thing I've ever seen and I love it and want more 😃
Finally I can trash my 30 y.o. PC that has all C64 dev tools.
1:01:26 lol