x86‑64 machine code on Linux, 97 B
input
/dev/stdin
connected to a pipe or terminal in cooked mode
- possibly empty input
- Linux zeros processor registers upon process initialization (with the obvious exception of
rsp
).
source code
Uses NASM – the Netwide Assembler.
Overview of algorithm:
- Infinitely wait until data become available. (
select
)
- Query total number of Bytes immediately available for reading. (
FIONREAD
)
- Discard input. (
TCIFLUSH
)
- Integer division: Bytes that were available for reading count divided by 10.
- Loop: Whole 10 Byte writes of
'HelloWorld'
.
- Write remainder of integer division Bytes.
global _start
bits 64
default rel
; These two constants rather serve as documentation:
STDIN_FILENO equ 0
STDOUT_FILENO equ 1
; Linux (`syscall`) system call numbers used in this program:
sys_exit equ 60
sys_ioctl equ 16
sys_select equ 23
sys_write equ 1
; `ioctl(2)` requests used:
FIONREAD equ 0x0000541B
TCFLSH equ 0x0000540B
; Argument to `TCFLSH` request:
TCIFLUSH equ 0
; ==============================================================
section .text
hello_world:
db 'Hello'
db 'World'
; 'HelloWorld'’s length is `10`, but this is cleaner style:
hello_world_length equ $ - hello_world
_start:
; rax rdi rsi rdx r10 r8
; select( 1, [rsp], NULL, NULL, NULL)
push 1 ; bitfield with LSB set
mov rsi, rsp ; *readfds
pop rdi ; nfds
push rdi ; rsp for get_iteration_count
mov al, sys_select
syscall
get_input_length:
; rax rdi rsi rdx
; ioctl(STDIN_FILENO, FIONREAD, [rsp])
xor edi, edi ; fd := 0 (STDIN_FILENO)
xchg rsi, rdx ; bytes available buffer
mov si, FIONREAD ; ioctl(2) request number
mov al, sys_ioctl
syscall
discard_input:
; rax rdi rsi rdx
; ioctl(STDIN_FILENO, TCFLSH, TCIFLUSH)
mov edx, edi ; TCIFLUSH = 0 → copy zero
mov sil, TCFLSH & 0xFF ; TCFLSH − 0x10 = FIONREAD
mov al, sys_ioctl
syscall
; `rax` may be non-zero now, in particular −ENOTTY.
test eax, eax ; for dumb last char = "\n" test
setns dil ; subtrahend = 0 for ENOTTY
get_iteration_count:
; Retrieve number of Bytes that were available for reading.
pop rax
sub eax, edi ; subtract 1 if this is terminal
jc exit ; user typed ^D (detach input)
; Load divisor.
push hello_world_length
pop rcx
; eax := number of whole writes; edx := Bytes in partial write
div ecx
; Save Bytes in partial write for `finish_write` below.
; The `push rdx`/`pop rdx` pattern is just two 2 Bytes.
push rdx
; Use `ebx` as loop counter, because `syscall` clobbers `rcx`
; and this does not require a REX prefix like r8 – r15 do.
xchg eax, ebx
write:
; rax rdi rsi rdx
; write(STDOUT_FILENO, hello_world, 10)
xchg edx, ecx
lea rsi, [hello_world]
mov dil, STDOUT_FILENO
test ebx, ebx ; zero _whole_ writes test
jz finish_write ; zero entire 10B writes
keep_writing:
mov al, sys_write
syscall
dec ebx
jnz keep_writing
; Write the remainining ≤ 9 Bytes.
finish_write:
pop rdx
mov al, sys_write
syscall
exit:
xchg ebx, edi ; rdi := 0 (= “successful”)
push sys_exit ; need to wipe entire register
pop rax ; because we may have jumped
syscall ; here from get_iteration_count
; vim: ft=nasm:
disassembly
helloWorld.o: file format ELF64-x86-64
Disassembly of section .text:
0000000000000000 <hello_world>:
0: 48 rex.W
1: 65 6C gs ins byte ptr es:[rdi], dx
3: 6C ins byte ptr es:[rdi], dx
4: 6F outs dx, dword ptr ds:[rsi]
5: 57 push rdi
6: 6F outs dx, dword ptr ds:[rsi]
7: 72 6C jb 75 <exit + 0x1B>
9: 64 fs
000000000000000A <_start>:
A: 6A 01 push 0x1
C: 48 89 E6 mov rsi, rsp
F: 5F pop rdi
10: 57 push rdi
11: B0 17 mov al, 0x17
13: 0F 05 syscall
0000000000000015 <get_input_length>:
15: 31 FF xor edi, edi
17: 48 87 F2 xchg rdx, rsi
1A: 66 BE 1B 54 mov si, 0x541B
1E: B0 10 mov al, 0x10
20: 0F 05 syscall
0000000000000022 <discard_input>:
22: 89 FA mov edx, edi
24: 40 B6 0B mov sil, 0xB
27: B0 10 mov al, 0x10
29: 0F 05 syscall
2B: 85 C0 test eax, eax
2D: 40 0F 99 C7 setns dil
0000000000000031 <get_iteration_count>:
31: 58 pop rax
32: 29 F8 sub eax, edi
34: 72 24 jb 5A <exit>
36: 6A 0A push 0xA
38: 59 pop rcx
39: F7 F1 div ecx
3B: 52 push rdx
3C: 93 xchg ebx, eax
000000000000003D <write>:
3D: 87 D1 xchg ecx, edx
3F: 48 8D 35 BA FF FF FF lea rsi, [rip + 0XFFFFFFFFFFFFFFBA]
46: 40 B7 01 mov dil, 0x1
49: 85 DB test ebx, ebx
4B: 74 08 je 55 <finish_write>
000000000000004D <keep_writing>:
4D: B0 01 mov al, 0x1
4F: 0F 05 syscall
51: FF CB dec ebx
53: 75 F8 jne 4D <keep_writing>
0000000000000055 <finish_write>:
55: 5A pop rdx
56: B0 01 mov al, 0x1
58: 0F 05 syscall
000000000000005A <exit>:
5A: 87 DF xchg edi, ebx
5C: 6A 3C push 0x3C
5E: 58 pop rax
5F: 0F 05 syscall
Having the 'HelloWorld'
string in the .text
section is weird, but putting (read-only) data into an already/definitely existing section, i. e. .text
, makes the executable file smaller.
The stripped ELF64 executable file has a size of merely 440 B, not that that really mattered for code golfing.
output
- output lacks of a trailing newline
HelloWorld
repeating until the input’s length is reached
- on a terminal input length − 1 accounting for a presumed terminating
"\n"
[even if the user actually detached from input Ctrl‑D
]
limitations
- I am not sure, but maybe the
FIONREAD
ioctl(2)
I am utilizing is capped at 0x7FFFF000
because one read(2)
can transfer at most this many Bytes? Therefore this program’s output will be limited to 2,147,479,552 Bytes accordingly.
- At any rate, there’s a 32-bit
div ecx
, so a string length ≥ 232 is not handled correctly.
- If we’re reading from a pipe, the pipe’s capacity may be the limiting factor. In
pipe(7)
, § pipe capacity, it says the default value is 65536 B (so it can be changed). Similarly, a terminal’s input buffer may have comparable “low” dimensions. Yet these are implementation details outside of this program.