fungsi kode mesin x86-64, 30 byte.
Menggunakan logika rekursi sama dengan jawaban C dengan @Level Sungai St . (Kedalaman rekursi maksimum = 100)
Menggunakan puts(3)
fungsi dari libc, yang biasanya dijalankan oleh executable normal. Itu bisa dipanggil menggunakan Sistem V86 ABI x86-64, yaitu dari C di Linux atau OS X, dan tidak mengalahkan register yang tidak seharusnya.
objdump -drwC -Mintel
output, berkomentar dengan penjelasan
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.
Dibangun dengan yasm -felf64 -Worphan-labels -gdwarf2 golf-googol.asm &&
gcc -nostartfiles -o golf-googol golf-googol.o
. Saya dapat memposting sumber NASM asli, tetapi sepertinya berantakan karena instruksi asm ada di sana di pembongkaran.
putchar@plt
kurang dari 128 byte dari jl
, jadi saya bisa menggunakan lompatan pendek 2-byte alih-alih 6-byte dekat, tapi itu hanya berlaku dalam eksekusi kecil, bukan sebagai bagian dari program yang lebih besar. Jadi saya tidak berpikir saya bisa membenarkan tidak menghitung ukuran implementasi libc jika saya juga mengambil keuntungan dari pengkodean jcc pendek untuk mencapainya.
Setiap level rekursi menggunakan ruang stack 24B (2 push dan alamat balik didorong oleh CALL). Setiap kedalaman lainnya akan memanggil putchar
dengan tumpukan hanya selaras dengan 8, bukan 16, jadi ini melanggar ABI. Implementasi stdio yang menggunakan toko yang disejajarkan untuk menumpahkan register xmm ke stack akan bermasalah. Tetapi glibc putchar
tidak melakukan itu, menulis ke pipa dengan buffering penuh atau menulis ke terminal dengan buffering baris. Diuji pada Ubuntu 15.10. Ini bisa diperbaiki dengan dummy push / pop di .loop
, untuk mengimbangi tumpukan dengan 8 lain sebelum panggilan rekursif.
Bukti bahwa ia mencetak jumlah baris baru yang benar:
# 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.
Versi pertama saya adalah 43B, dan digunakan puts()
pada buffer 9 baris baru (dan terminasi 0 byte), jadi put akan menambahkan tanggal 10. Basis rekursi itu bahkan lebih dekat dengan inspirasi C.
Anjak 10 ^ 100 dengan cara yang berbeda mungkin bisa memperpendek buffer, mungkin turun ke 4 baris baru, menghemat 5 byte, tetapi menggunakan putchar jauh lebih baik. Ini hanya membutuhkan integer arg, bukan pointer, dan tidak ada buffer sama sekali. Standar C memungkinkan implementasi untuk makro putc(val, stdout)
, tetapi dalam glibc ia ada sebagai fungsi nyata yang dapat Anda panggil dari asm.
Mencetak hanya satu baris baru per panggilan, bukan 10 hanya berarti kita perlu meningkatkan kedalaman maks rekursi sebesar 1, untuk mendapatkan faktor lain dari 10 baris baru. Karena 99 dan 100 keduanya dapat diwakili oleh tanda yang langsung diperpanjang 8-bit, push 100
masih hanya 2 byte.
Lebih baik lagi, memiliki 10
dalam register berfungsi sebagai baris baru dan penghitung lingkaran, menghemat satu byte.
Gagasan untuk menyimpan byte
Versi 32-bit dapat menyimpan byte untuk dec edi
, tetapi konvensi pemanggilan stack-args (untuk fungsi pustaka seperti putchar) membuat tail-call berfungsi lebih mudah, dan mungkin akan membutuhkan lebih banyak byte di lebih banyak tempat. Saya dapat menggunakan konvensi register-arg untuk privat f()
, hanya dipanggil oleh g()
, tetapi kemudian saya tidak dapat memanggil-putchar (karena f () dan putchar () akan mengambil jumlah stack-arg yang berbeda).
Mungkin saja f () mempertahankan status pemanggil, alih-alih melakukan save / restore di pemanggil. Namun, itu mungkin menyebalkan, karena mungkin perlu terpisah di setiap sisi cabang, dan tidak kompatibel dengan panggilan ekor. Saya mencobanya tetapi tidak menemukan penghematan.
Menjaga loop counter pada stack (bukannya push / popping rcx di loop) tidak membantu. Itu 1B lebih buruk dengan versi yang menggunakan put, dan mungkin bahkan lebih dari kerugian dengan versi ini yang membuat rcx lebih murah.