I've recently been "getting into" the demoscene. Getting into is in quotes because I live in North America and there are no demoparties within 50 km of me, so all I can do is read old Hugi articles and watch videos from pouet.
In the interest of doing something productive, I decided to make my own demo.
The goal for my demo was to get it less than 4k. I don't know openGL, but I do know how to make audio samples. I decided I would make an audio only demo. Concurrently, I've also been steadily going through a massive PDF of old perl golf tournaments. I've always been fascinated by how tiny you can make programs in perl. Then I thought "hey, what if I wrote part of my demo in perl?"
So I did. I wrote a little perl script that generates samples, and when you play the samples back you get a proportionally little song. I didn't want to make the demo entirely in perl and trust the interpreter directive because thats no fun. Instead, I wrote a kind of "launcher" in c that does the following:
- Stores a gzipped compressed version of the perl code in the data segment.
- Opens up a connection to the audio server.
- Sets up a piping system like this: c-code | gzip | perl | c-code
- Starts writing the gzipped perl code into gzip, gzip pipes it to perl, perl executes it.
- Reads the samples generated by the perl code and sends them to the audio server
The perl code is about 1.5k, gzipped its about 700 bytes. The c code, however, is 2.3k, and when you compile it with gcc -o audio audio.c you get an executable thats over 16k.
The executable is big for a few reasons. Firstly, there is a lot of debug information in the executable that programs like gdb use to get line number information. Secondly, when you use a lot of library functions, as is necessary for opening sound server connections, the ELF's Procedure Linkage Table (PLT) grows large. Thirdly, the c startfiles (crt0 and the like) tend to be very disproportionately big in comparison to a tiny program.
Fixing the first problem is done by adding extra options to the gcc command. The options -s -g0 -Os strip useless information, disable debug info, and enable size optimization respectively. This shaved about 6k off my program.
The second problem can be avoided by doing whats called "manual linking." This stackoverflow answer pointed me in the right direction. What you do is, instead of trusting the linker to find the function offsets for you, you do them yourself with calls to dlopen and dlsym. These functions give you pointers to functions. This ended up cutting a few hundred bytes off my executable, because now the PLT only has these two functions in it.
Finally, I stopped using startfiles altogether by adding the options -nostartfiles -nostdlib to my compiler string. This brought the program almost the rest of the way, but I was still sitting a touch above my goal of 4k. It also started to segfault every time I connected to the audio server.
It stopped working because (I'm hypothesizing here) pulseaudio assumes startfiles are being used, so it tries to access memory it assumes was allocated by them. Or, its following a pointer to nowhere. I don't know. The point is, it breaks.
To solve the problem I did something pretty bad. Often when you install ALSA or pulseaudio you get some utility programs as well. Specifically I'm talking about aplay and paplay. Both are command line programs that send raw data to the sound server. I decided to offload the actual playback stuff to one of these programs, using the c code to exclusively set up the gzip | perl | aplay/paplay scaffolding.
By assuming the existence of certain programs on the system, I was able to get my program well below 4k (3712 bytes). I think I can get that value smaller by re-writing the thing in assembly, because the only functions I'm using are system calls.
I've put the c file, the header that includes the gzipped perl code and a makefile into this zip file so you can enjoy the song yourself.