x86 Machine Code (32-bit protected mode), 19 bytes
8D 34 B7 39 F7 73 0C 83 EE 04 8B 07 03 06 89 06 AB EB F0 C3
The above bytes of code define a function that takes two parameters (a pointer to the array in the EDI
register, and the length of the array in the ESI
register), modifies the pointed-to array to contain the "reverse array sum", and then returns. It does not return a value to the caller.
(This is a custom calling convention used to receive the arguments. It is actually the standard calling convention used on Gnu/Unix systems for x86-64 binaries, but in x86-32, arguments are typically passed on the stack. That takes more bytes to encode, and is less efficient, so we want to ensure that the arguments are passed to us in registers. As far as I understand the rules, this is completely legal. We don't need to conform to a particular standardized calling convention. Certainly, when writing assembly, the programmer is free to define her own calling conventions, unless she needs to interoperate with C code.)
Note that this function also assumes the direction flag is cleared (DF
== 0) upon entry to the function. This is a sensible assumption, as it is guaranteed by most platform ABIs, including the Linux x86 32-bit ABI. If you need the code to work under circumstances where the state of DF
cannot be assumed, then you need to add a 1-byte CLD
instruction to the top of the function.
Ungolfed assembly mnemonics:
; void ReverseArraySum(int *pArray, int length);
8D 34 B7 lea esi, [edi+esi*4] ; compute back pointer
Loop:
39 F7 cmp edi, esi
73 0C jae End ; finished when EDI >= ESI
83 EE 04 sub esi, 4
8B 07 mov eax, DWORD PTR [edi]
03 06 add eax, DWORD PTR [esi]
89 06 mov DWORD PTR [esi], eax
AB stosd ; equivalent to 'mov DWORD PTR es:[edi], eax' + 'add edi, 4'
EB F0 jmp Loop
End:
C3 ret
Conceptually, the code is pretty straightforward. We just iterate through the array using two pointers. The first pointer is passed to us as an argument, in the register EDI
, and it is a pointer to the beginning of the array. The second pointer is computed (initial LEA
instruction) by adding the address of the beginning of the array to the length of the array, scaled by the size of an element in the array (4 bytes for an int
on x86-32). Thus, ESI
is a pointer to the end of the array.
At the top of the Loop
, we check the pointers to see if we should keep looping or if we are finished. Normally, we'd want to save bytes by putting this test at the end of the loop (and eliminating the JMP
you see at the end now), but we can't do that because the challenge requires us to handle an empty input array.
Inside the body of the loop, we:
- Eagerly subtract 4 bytes (the size of a single element in the array) from the back pointer,
ESI
.
- Retrieve and store the value of the element pointed to by the front pointer (
EDI
) in a temporary register (EAX
).
- Increment
EAX
by the value of the element pointed to by the back pointer (ESI
).
- Store the sum (
EAX
) in the element pointed to by the back pointer (ESI
).
- Store the same sum (
EAX
) in the element pointed to by the front pointer (EDI
), while simultaneously incrementing that pointer (EDI
) by 4 bytes (the size of a single element in the array). The handy x86 string instruction STOSD
is what allows us to do both of those things in only a single byte of code.
(With the caveat given at the outset, that the direction flag is cleared, and also the assumption that we are running in flat mode, where the "extra" segment (ES
) is identical to the data segment (DS
).)
Finally, we're done and we return to the caller, without returning any value.
__
You can see it run on TIO, but note that the TIO link uses a C wrapper to exercise the machine code, and GCC won't necessarily respect our custom calling convention, so we have to add extra code at the top of the function to retrieve the parameters from the stack and put them into the appropriate registers.