The call/return function calling protocol has some complex interactions with register allocation, specifically the gen & kill sets. Here it is, laid out with why each matters. Note that the lecture4 slides are wrong in their definition of caller and callee save registers. old definitions: caller save = ecx edx eax callee save = ebx esi edi args = ecx edx eax return = eax new definitions, move ebx to callers save caller save = ecx edx eax ebx callee save = esi edi args = ecx edx eax return = eax Here's the chart of what needs to be where | gen | kill ---------------+--------------------------+------------------------ (call s) | args^5, s | caller save^3, return^4 (tail-call s) | args^5, callee save^2, s | (return) | callee save^2, return^1 | The "^n"s in the table above match up to explanations below: ^1: what if there are instructions between the assignment to eax and the return (for example, if you are returning the third argument)? Without this gen, then the register allocator is free to use eax for a temporary inbetween. ^2: enforces the calling convention (ie that callee save registers are actually saved) ^3: the call instruction may assign to the caller save registers, so we need to make sure they interfere with any variables whose live ranges span the call instruction. ^4: Consider a sequence like this: (x <- ...) (call ...) (... <- x) Without a kill to eax there, x might get assigned to eax, which would be bad. ^5: Consider a sequence of instructions like this: (ecx <- ...) (f <- ...) (call f) We have to be sure that f does not get assigned to ecx. Of course, your compiler probably does not generate code like that. But watch out when spilling happens. If you have a sequence like this: (ecx <- ...) (call f) and f gets spilled, then you can end up with things like the above.