As 4386427's previous answer pointed out, you cannot mix 32- and 64-bit registers in an effective address. The CPU doesn't support that. So addl (%rdi,%edx,4), %eax would not be encodeable.
To use i as the index part of an effective address, we need it in a 64-bit register. Since i is of type int, which is signed, the compiler sign-extends it with movsx. And it uses a separate register %rcx so that %edx can continue to hold the value of the variable i, making it easier for a debugger to inspect this value (e.g. print i in gdb).
As it turns out, we can prove that i will always be nonnegative in this function. The initial movl $0, %edx also zeros out the high half of %rdx, and it will remain zero from then on, so in fact %rdx does always contain the correct 64-bit value of the variable i. Thus we could have used addl (%rdi, %rdx, 4), %eax instead, and omitted the movsx. The compiler probably didn't make that deduction at this level of optimization, though.
(It is also possible to use all 32-bit registers in an effective address with an address size override prefix, so addl (%edi, %edx, 4), %eax is an encodeable instruction, but it won't work since it would truncate the high 32 bits of the pointer arr in %rdi. For this reason the address size override is hardly ever useful in 64-bit code.)