x86-64 machine code function, 30 bytes.
Uses the same recursion logic as the C answer by @Level River St. (Max recursion depth = 100)
Uses the puts(3)
function from libc, which normal executables are linked against anyway. It's callable using the x86-64 System V ABI, i.e. from C on Linux or OS X, and doesn't clobber any registers it's not supposed to.
objdump -drwC -Mintel
output, commented with explanation
0000000000400340 <g>: ## wrapper function
400340: 6a 64 push 0x64
400342: 5f pop rdi ; mov edi, 100 in 3 bytes instead of 5
; tailcall f by falling into it.
0000000000400343 <f>: ## the recursive function
400343: ff cf dec edi
400345: 97 xchg edi,eax
400346: 6a 0a push 0xa
400348: 5f pop rdi ; mov edi, 10
400349: 0f 8c d1 ff ff ff jl 400320 <putchar> # conditional tailcall
; if we don't tailcall, then eax=--n = arg for next recursion depth, and edi = 10 = '\n'
40034f: 89 f9 mov ecx,edi ; loop count = the ASCII code for newline; saves us one byte
0000000000400351 <f.loop>:
400351: 50 push rax ; save local state
400352: 51 push rcx
400353: 97 xchg edi,eax ; arg goes in rdi
400354: e8 ea ff ff ff call 400343 <f>
400359: 59 pop rcx ; and restore it after recursing
40035a: 58 pop rax
40035b: e2 f4 loop 400351 <f.loop>
40035d: c3 ret
# the function ends here
000000000040035e <_start>:
0x040035e - 0x0400340 = 30 bytes
# not counted: a caller that passes argc-1 to f() instead of calling g
000000000040035e <_start>:
40035e: 8b 3c 24 mov edi,DWORD PTR [rsp]
400361: ff cf dec edi
400363: e8 db ff ff ff call 400343 <f>
400368: e8 c3 ff ff ff call 400330 <exit@plt> # flush I/O buffers, which the _exit system call (eax=60) doesn't do.
Built with yasm -felf64 -Worphan-labels -gdwarf2 golf-googol.asm &&
gcc -nostartfiles -o golf-googol golf-googol.o
. I can post the original NASM source, but that seemed like clutter since the asm instructions are right there in the disassembly.
putchar@plt
is less than 128 bytes away from the jl
, so I could have used a 2-byte short jump instead of a 6-byte near jump, but that's only true in a tiny executable, not as part of a larger program. So I don't think I can justify not counting the size of libc's puts implementation if I also take advantage of a short jcc encoding to reach it.
Each level of recursion uses 24B of stack space (2 pushes and the return address pushed by CALL). Every other depth will call putchar
with the stack only aligned by 8, not 16, so this does violate the ABI. A stdio implementation that used aligned stores to spill xmm registers to the stack would fault. But glibc's putchar
doesn't do that, writing to a pipe with full buffering or writing to a terminal with line buffering. Tested on Ubuntu 15.10. This could be fixed with a dummy push/pop in the .loop
, to offset the stack by another 8 before the recursive call.
Proof that it prints the right number of newlines:
# with a version that uses argc-1 (i.e. the shell's $i) instead of a fixed 100
$ for i in {0..8}; do echo -n "$i: "; ./golf-googol $(seq $i) |wc -c; done
0: 1
1: 10
2: 100
3: 1000
4: 10000
5: 100000
6: 1000000
7: 10000000
8: 100000000
... output = 10^n newlines every time.
My first version of this was 43B, and used puts()
on a buffer of 9 newlines (and a terminating 0 byte), so puts would append the 10th. That recursion base-case was even closer to the C inspiration.
Factoring 10^100 a different way could maybe have shortened the buffer, maybe down to 4 newlines, saving 5 bytes, but using putchar is better by far. It only needs an integer arg, not a pointer, and no buffer at all. The C standard allows implementations where it's a macro for putc(val, stdout)
, but in glibc it exists as a real function that you can call from asm.
Printing only one newline per call instead of 10 just means we need to increase the recursion max depth by 1, to get another factor of 10 newlines. Since 99 and 100 can both be represented by a sign-extended 8-bit immediate, push 100
is still only 2 bytes.
Even better, having 10
in a register works as both a newline and a loop counter, saving a byte.
Ideas for saving bytes
A 32-bit version could save a byte for the dec edi
, but the stack-args calling convention (for library functions like putchar) makes tail-call work less easily, and would probably require more bytes in more places. I could use a register-arg convention for the private f()
, only called by g()
, but then I couldn't tail-call putchar (because f() and putchar() would take a different number of stack-args).
It would be possible to have f() preserve the caller's state, instead of doing the save/restore in the caller. That probably sucks, though, because it would probably need to get separately in each side of the branch, and isn't compatible with tailcalling. I tried it but didn't find any savings.
Keeping a loop counter on the stack (instead of push/popping rcx in the loop) didn't help either. It was 1B worse with the version that used puts, and probably even more of a loss with this version that sets up rcx more cheaply.