I have some ideas about how you might be able to improve your program.
Rather than trying to deal with the problem for every instruction, one approach is avoiding it entirely. One way to do that is to simply append a number of bytes to the end of the vector. If the maximum bytes for an instruction is \$n\$, then append \$n-1\$ bytes to the ...
You have all the elements. The only thing that can be eliminated is jmp .run, as follows:
mov si, Prompt
puts: mov ah, 0xe
mov bx, 7
or al, al
.post: int 0x10
Prompt: db 'Hello World', 0
I'm mostly reviewing the code-gen choices, not the style / implementation of the compiler itself.
The compiler itself mostly looks fine in the parts I've skimmed over (looking for how it uses the format strings). The design is pretty clear and easy to dive into, and the C is well-formatted. It could be simpler and/or more efficient in some cases. (e.g. ...
I think the big switch is a problem. Consider a more data-driven approach where each opcode is described by a struct:
unsigned char command_byte;
unsigned char mask; // if only a subset of bits determine command
unsigned char length;
// other members as needed - e.g. a pointer to the "print" function
Now, the code that ...
The code looks straight-forward and really optimized for size and simplicity.
There's a small detail that I would change, though: replace cmove with cmovz, to make the code more expressive. It's not that "being equal" would be of any interest here, it's the zeroness of %ecx that is interesting.
I like the omitted second jmp in memmove. It's obvious after ...
feedback on how clear my comments were and how easy the code was to follow
I'm not a big fan of line comments. I think they make the code a bit harder to follow. That's why I prefer tail comments along with a nice tabular layout of the labels, instructions, operands, and tail comments.
And of course don't write redundant comments like "Update the ...
I was thinking if I should add labels like write even if I wouldn't jmp to them, so code would be cleaner.
Writing labels that aren't jumped to is fine. Provided they have meaningful names, it can help to understand the program.
My comments (not in a particular order)
mov esi, 1
This is a redundant comment! I already can see what the instruction ...
If you execute this code with n DB 0, it returns without setting ch to any value, leaving the result as undefined.
If n happens to be between 128 and 255, the jge will treat the comparison as if it was a signed comparison, and since 0 is not greater than or equal to any value between ...
Barring any compiler heroics, you are computing n*n a total of \$n^3\$ times. You might want to cache that result.
const int nn = n*n;
B[j + n_times_i] is a linearly increasing address location, given that j increases by 1 for each middle loop, and and i increases once for each outer loop, which is n increases of j. Taking advantage of that, you can skip ...
You're treating comparison of addresses as signed values, instead of unsigned. For example, after you execute cmp bufferOffset, bufferTail, you use the jge condition, which is jump greater or equal. This is intended for signed values. You should use jae jump above or equal instruction instead. This condition needs to be changed in several places.
mov SS, AX ;
mov BP, 0x7BFF
mov SP, BP
This is a dangerous construct! To maintain a consistent SS:SP pair of registers you should always set these back to back (No instruction(s) in between).
The code uses pusha, popa, shr bx, 12, ...
So your code is clearly targetting x86 (and not the infamous 8086). Then you don't need to use ...
Linux has the sendfile system call, which copies data between file descriptors instead of copying the data to userspace and back to kernel space. That may be more efficient.
Your code looks very clean and organized. If you had used named constants instead of magic numbers, you might not even need some of the comments:
mov rdx, tax
mov rsi, but
mov dl, byte [ecx]
cmp dl, 0; read byte and comp to 0
There are various alternatives, for example:
cmp byte [ecx], 0
mov dl, [ecx] ; note: BYTE would be redundant, but you can put it if you like
test dl, dl
For that loop overall,
mov dl, byte [ecx]
cmp dl, 0; read byte and comp to 0
There's plenty to optimize here!
In NASM you get the address simply by writing mov di, prompt. This has a shorter encoding than lea di, [prompt]. (In MASM this would be mov di, offset prompt giving the same benefit over the lea form).
Instead of writing the pair mov ah, 0x4c mov al, 0, you could combine these in 1 instruction as mov ax, 0x4C00. This ...
The operation count can be reduced, by using that some bits are moved by the same distance. Bits 0, 1 and 2 can be shifted left in one go. 5 and 6 can be shifted right in one go.
Some shift/bitwise-OR combinations can be written as lea, that would be bad for Pentium 4 but very good on Ryzen and Ice Lake and fine on Haswell/Skylake. Since this is 64bit code ...
My equivalent to your nice quine is a bit nicer, and differs in other ways.
It is nicer in that the payload at the end of the source is split into several lines. It still uses over-wide lines because the indentation and the blanks before comments are repeated verbatim, and each line is prefixed with a db " directive (the prefix) that is itself indented.
There's a bug in your code if memchr finds %sil in the last byte of %rdi; if %rcx tests to be zero and yet the byte has been found, it will incorrectly return zero.
To fix that, do something like this:
mov %rdx, %rcx
movzbl %sil, %eax
lea -1(%rdi), %rax
test %cl, %cl
Here's the 16-bit 8086 source to my bit mirror function, inputs are bx:dx for the size (question wants 32 there) and dword [hhvar] for the data to be bit-mirrored, output is in bx:dx. Like vnp also suggested I use rcl. I shift right bx:dx and then rotate-with-carry-left into si:di.
mov cx, dx
test bx, bx
Why are you so keen on zeroing the registers at the completion of the program? Drop those and you'll have made your first optimization already.
Don't use mov si, 0 to zero the register. Use xor si, si. It's shorter (codesize) and faster (execution speed).
Instead of using lea bx, array use mov bx, array (NASM style) or mov bx, offset array (...
Here are some things that may help you improve your program
Use consistent formatting
The code as posted has irregular indentation, making it not so easy to read. Assembly language programs are typically very linear and neat. Also, I personally don't use tab characters in my code so that it looks the same everywhere (including printing), but that's a ...
A code-size optimization
If you move the mov edi, Metrics instruction to just below the FindMatch label and thus have it repeat with each iteration, you can remove 4 instructions from the code. I've marked these with an exclamation mark:
xor edx, edx
mov ecx, edx
mov edi, Metrics ;Restore it from here
Your code's division into functions isn't very natural. The objective is to have each function performing one specific task, which should also be as orthogonal as possible to the tasks performed by the other functions. For instance, your disassemble function performs three different functions: it reads from the instruction stream, it interprets assembly code ...
It's fine to write this scan macro for educational purposes, but I think that considering its length a subroutine would have been more appropriate. The macro's bytes are inserted everywhere the macro is invoked. That adds up! Having that many duplicated bytes in a program is a waste of space.
mov ah, 0Eh ; print the ...
Ok, old question. But no accepted answer and I've got some thoughts, so...
My first thought on reading this code was to suggest you re-arrange your compares, so that you can "drop through" rather than doing an additional jump. If you move this to the end of your compare block:
cmp currentCharacter, DOUBLE_QUOTE ; compare [currentCharacter] to ...
Line up instructions and operands in vertical columns to improve readability (like you've done with the comments); e.g.:
mov var6, WINDOWS_NULL ; put pointer to template handle into parameter slot 6
call CreateFile ; create file handle
cmp rax, WINDOWS_INVALID_HANDLE ; validate file handle
When setting up the SS:SP registers, there is a slight chance that an interrupt might happen between setting SS and SP. The interrupt handler would then write to an unintended memory address. To prevent this, enclose the code between cli and sti.
I'd rather set SP to 07FEh, to align memory reads to 2-byte boundaries.
Instead of jmp $ you should not waste ...
a. Shortest minus 1
The Q4 program contains a 2-byte xor dx,dx that you can rapidly replace by
the 1-byte cwd instruction. Just bring that mov ax,484Eh on top. The
positive number in AX will make cwd clear DX.
B8 4E 48 mov ax,484Eh (*)
B2 FC mov dl,252
89 D7 mov di,dx
D1 E8 shr ax,1
89 45 21 mov [di+33],ax
or rcx, 0xFFFFFFFFFFFFFFFF
This is an optimization for size, but it does not break the dependency on the old value of rcx (at least not yet, maybe someday, it's not an impossible feature). That's a minor point compared to overall cost of a strlen but it's something to know so you can make the choice deliberately.
Should be redundant, typical calling ...