Re: How come simple "closures" aren't compiled?

  • From: Las <lasssafin@xxxxxxxxx>
  • To: luajit@xxxxxxxxxxxxx
  • Date: Sun, 4 Dec 2016 18:18:56 +0100

Yeah, that's what I meant.
I know it can compile calls, but it can't compile FNEW and UCLO of simple
functions.
The reason I'm asking is because obviously such "closures" can be used to
simplify APIs.
Like:

local t = {1, 2, 3};
foreach(t, function(i, n) return n * 2 end);
assert(t[1] == 2 and t[2] == 4 and t[3] == 6);

If this was in a loop, it would perform horrendously, even though the
closure isn't actually a closure and just a normal and pure function.

Las

On 4 December 2016 at 15:48, Peter Cawley <corsix@xxxxxxxxxx> wrote:

Luke: I think the question was why FNEW and UCLO bytecodes aren't yet
supported by the JIT compiler.

On Sun, 4 Dec 2016 at 14:44, Luke Gorrie <luke@xxxxxxxx> wrote:

On 3 December 2016 at 11:50, Las <lasssafin@xxxxxxxxx> wrote:


For example:

local n = 0;
local f = function() return 1 end
n = f();

Couldn't f be compiled pretty easily, since it doesn't depend on any
upvalues?


This code can JIT but there are a couple of reasons it did not here:

The JIT waits until a function has been called often enough to become
"hot" before it bothers to compile it;

In practice most functions are not compiled in isolation but rather
inlined into a larger "trace" and compiled together with their callers.


If you want to force the JIT to compile the f() function in isolation
then you could change the example to call it in a loop and also ensure that
the loop itself is not compiled (because then it would inline f() instead
of compiling it separately):

-- simple.lua
local n = 0;
local f = function() return 1 end

-- Create a function that will call f() in a loop
local loop = function ()
   for i = 1, 1000 do
      n = f()
   end
end

-- Disable JIT for the loop (to avoid noise.)
jit.off(loop)

-- Run the loop.
loop()


This will generate machine code for f():

$ ./luajit -jdump simple.lua
---- TRACE 1 start simple.lua:2
0001  KSHORT   0   1
0002  RET1     0   2
---- TRACE 1 IR
---- TRACE 1 mcode 42
0bcbffcf  mov dword [0x405ed410], 0x1
0bcbffda  movsd xmm7, [0x40604d68]
0bcbffe3  movsd [rdx], xmm7
0bcbffe7  xor eax, eax
0bcbffe9  mov ebx, 0x405f5300
0bcbffee  mov r14d, 0x405edfe0
0bcbfff4  jmp 0x00420eb9
---- TRACE 1 stop -> return


You could make the example slightly more juicy by instead calling f() in
a loop, summing the return values, and allowing the JIT to operate on the
loop:

-- simple2.lua
local n = 0;
local f = function() return 1 end
for i = 1, 100 do n = n + f() end


Then the whole loop, including the inlined call to f(), will compile into
these four instructions:

->LOOP:
0bcbffe0  addsd xmm7, xmm0
0bcbffe4  add ebp, +0x01
0bcbffe7  cmp ebp, 0x3b9aca00
0bcbffed  jle 0x0bcbffe0 ->LOOP

where the first instruction (addsd xmm7, xmm0) is the entire code for
the statement n = n + f().

That is not bad: calling your function, returning its result, and adding
it to the accumulator all with one instruction :-)

Cheers,
-Luke



Other related posts: