Given the following code:
defmodule Test do
def test(x, y) do
x + y
end
def test_first do
x1 = test(1, 1)
x2 = test(x1, 1)
test(x2, 1)
end
def test_last do
x1 = test(1, 1)
x2 = test(1, x1)
test(1, x2)
end
end
We get the following assembly (stripped down for brevity)
{function, test_first, 0, 9}.
{label,8}.
{func_info,{atom,'Elixir.Test'},{atom,test_first},0}.
{label,9}.
{allocate,0,0}.
{move,{integer,1},{x,1}}.
{move,{integer,1},{x,0}}.
{call,2,{f,7}}.
{move,{integer,1},{x,1}}.
{call,2,{f,7}}.
{move,{integer,1},{x,1}}.
{call_last,2,{f,7},0}.
{function, test_last, 0, 11}.
{label,10}.
{func_info,{atom,'Elixir.Test'},{atom,test_last},0}.
{label,11}.
{allocate,0,0}.
{move,{integer,1},{x,1}}.
{move,{integer,1},{x,0}}.
{call,2,{f,7}}.
{move,{x,0},{x,1}}.
{move,{integer,1},{x,0}}.
{call,2,{f,7}}.
{move,{x,0},{x,1}}.
{move,{integer,1},{x,0}}.
{call_last,2,{f,7},0}.
The last-argument-chaining version has the extra move instruction to move from the return position in X0 to the last argument position in X1. But as I said before, this level of difference, probably wonât matter in real-life programs, since there are additional instruction-fusions and optimisations going on in the loader.