4 ms·
This is the heart of this program. It aims to be able to produce a sustained output rate of 64 bytes of FizzBuzz per four clock cycles in its main loop
by Scaevolus 5y ago
This is the heart of this program. It aims to be able to produce a
sustained output rate of 64 bytes of FizzBuzz per four clock cycles
in its main loop (with frequent breaks to do I/O, and rare breaks
to do more expensive calculations).
The third phase operates primarily using a bytecode interpreter; it
generates a program in "FizzBuzz bytecode", for which each byte of
bytecode generates one byte of output. The bytecode language is
designed so that it can be interpreted using SIMD instructions; 32
bytes of bytecode can be loaded from memory, interpreted, and have
its output stored back into memory using just four machine
instructions. This makes it possible to speed up the FizzBuzz
calculations by hardcoding some of the calculations into the
bytecode (this is similar to how JIT compilers can create a version
of the program with some variables hardcoded, and throw it away on
the rare occasions that those variables' values change).
The bytecode format is very simple (in order to allow it to be
interpreted in just a couple of machine instructions):
- A negative byte represents a literal character (e.g. to produce
a literal 'F', you use the bytecode -'F', i.e. -70 = 0xba)
- A byte 0..7 represents the hundreds..billions digit of the line
number respectively, and asserts that the hundreds digit of the
line number is even
- A byte 8..15 represents the hundreds..billions digit of the line
number respectively, and asserts that the hundreds digit of the
line number is odd
In other words, the bytecode program only ever needs to read from
LINENO_MID; the information stored in LINENO_LOW and LINENO_TOP
therefore has to be hardcoded into it. The program therefore needs
to be able to generate 600 lines of output (as the smallest number
that's divisible by 100 to be able to hardcode the two low digits,
200 to be able to get the assertions about the hundreds digits
correct, and 3 and 5 to get the Fizzes and Buzzes in the right
place).
The bytecode interpreter consists of four instructions:
1. Load the bytecode from memory into %ymm2;
2. Use it as a shuffle mask to shuffle LINENO_MID_TEMP;
3. Subtract the bytecode from the shuffle result;
4. Output the result of the subtraction.
#define INTERPRET_BYTECODE(bc_offset, buf_offset) \
vmovdqu %ymm2, [%rdx + bc_offset]; \
vpshufb %ymm0, LINENO_MID_TEMP, %ymm2; \
vpsubb %ymm0, %ymm0, %ymm2; \
vmovdqa [OUTPUT_PTR + buf_offset], %ymm0