Skip to content

JIT misbehaviour in the metamethod invocation for constructors #1441

@Buristan

Description

@Buristan

Originally reported by @locker.

Assume we have the following two chunks of code:

/tmp/ffi_meta1.lua:

local ffi = require('ffi')

ffi.cdef[[
  struct test {int a;};
]]

local test_t = ffi.typeof('struct test')

local function index_func(v)
  -- Should raise an error (stack overflow).
  return ffi.typeof(v).a
end

ffi.metatype(test_t, {
  __index = index_func
})

local function newobj()
  return ffi.new(test_t, 0)
end

local o1 = newobj()
jit.flush()

jit.opt.start('hotloop=1')

local r, e
for _ = 1, 4 do
  r, e = pcall(index_func, o1)
end
print(r,e)
src/luajit /tmp/ffi_meta1.lua
LuaJIT ASSERT lj_record.c:165: rec_check_slots: slot 2 type mismatch: stack type 4 vs IR type 19
Aborted                    (core dumped) src/luajit /tmp/ffi_meta1.lua

src/luajit -joff /tmp/ffi_meta1.lua
false   stack overflow

/tmp/ffi_meta2.lua (the same, but avoid recursion and stack overflow):

local ffi = require('ffi')

ffi.cdef[[
  struct test {int a;};
]]

local test_t = ffi.typeof('struct test')

local function newobj()
  return ffi.new(test_t, 0)
end

local o1 = newobj()

local function index_func(v)
  if v == o1 then
    local x = ffi.typeof(v).a
    return x
  else
    return 2
  end
end

ffi.metatype(test_t, {
  __index = index_func
})

jit.flush()

jit.opt.start('hotloop=1')

local r
for _ = 1, 4 do
  r = index_func(o1)
end
print(r)
src/luajit /tmp/ffi_meta2.lua
LuaJIT ASSERT lj_record.c:165: rec_check_slots: slot 1 type mismatch: stack type 13 vs IR type 11
Aborted                    (core dumped) src/luajit /tmp/ffi_meta2.lua

src/luajit -joff /tmp/ffi_meta2.lua
2

The JIT part doesn't invoke the metamethod for the CType. Instead it gets the field of the structure referenced by this CType.

The simplest patch for the JIT side is the following:

diff --git a/src/lj_crecord.c b/src/lj_crecord.c
index b016eaec..538847e6 100644
--- a/src/lj_crecord.c
+++ b/src/lj_crecord.c
@@ -886,7 +886,7 @@ again:
 	    ptr = emitir(IRT(IR_ADD, IRT_PTR), ptr, lj_ir_kintp(J, ofs));
 	  crec_index_bf(J, rd, ptr, fct->info);
 	  return;
-	} else {
+	} else if (cd && cd->ctypeid != CTID_CTYPEID) {
 	  lj_assertJ(ctype_isfield(fct->info), "field expected");
 	  sid = ctype_cid(fct->info);
 	}

OTOH, maybe it is more clear to make logic in the VM match the JIT behaviour (the semantic provided by 6cee133 isn't obvious to me for that case).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions