Skip to content

Commit 30e7c97

Browse files
Buristanigormunkin
andcommitted
Fix moving dead slots in FFI finalizers table
LuaVela has the internal weak table to keep the mapping for GCcdata objects to their finalizers, which are set via <ffi.gc>. When GCcdata object becomes dead, there is still the key in this weak table until it's finalized by the Lua garbage collector (more precisely, until the next GCfinalize phase). When the table reallocation occurs, these keys are copied intact to the new hash part. LuaVela uses <copyTV> for such move, but there is just a plain pointer assignment in LuaJIT. As a result, the assertion within <tvchecklive> (to be more precisely within <isdead> check) hits. This patch replaces <copyTV> with the simple pointer move. Relates to tarantool/tarantool#5101 Co-authored-by: Igor Munkin <imun@cpan.org> Signed-off-by: Igor Munkin <imun@cpan.org>
1 parent ead2c14 commit 30e7c97

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed

src/lj_tab.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,8 @@ TValue* lj_tab_newkey(lua_State *L, GCtab *t, const TValue *key) {
594594
n = freenode;
595595
}
596596
}
597-
copyTV(L, &n->key, key);
597+
/* XXX: Do not use copyTV here: key may be dead (e.g. while rehashing). */
598+
n->key = *key;
598599
if (LJ_UNLIKELY(tvismzero(&n->key))) { setrawV(&n->key, 0); }
599600
lj_gc_anybarriert(L, t);
600601
lua_assert(tvisnil(&n->val));
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
-- This is a part of uJIT's testing suite.
2+
-- Copyright (C) 2020-2025 LuaVela Authors. See Copyright Notice in COPYRIGHT
3+
-- Copyright (C) 2015-2020 IPONWEB Ltd. See Copyright Notice in COPYRIGHT
4+
5+
local ffi = require('ffi')
6+
pcall(jit.off)
7+
8+
ffi.cdef([[
9+
struct old {int a;};
10+
struct new {int a;};
11+
]])
12+
13+
local anchor = 0
14+
local function create_fin(i)
15+
return function()
16+
anchor = anchor + i
17+
end
18+
end
19+
20+
-- Start GC and collect the existing garbage.
21+
collectgarbage('collect')
22+
-- Make GC collect as aggressive as possible.
23+
collectgarbage('setstepmul', 100)
24+
25+
-- Create and mark GCcdata object as dead.
26+
-- As a result, ctype_state->finalizer->hmask is 1 (i.e. 2 fields):
27+
-- * __mode = "k";
28+
-- * finalizer for old GCcdata.
29+
local old_type = ffi.metatype('struct old', { __gc = create_fin(1) })
30+
local _ = old_type(1)
31+
_ = nil
32+
33+
-- Reset the GC related counters.
34+
ujit.getmetrics()
35+
-- Finish all GCSpropagate steps and GCSatomic step.
36+
while ujit.getmetrics().gc_steps_sweepstring == 0 do
37+
collectgarbage('step')
38+
end
39+
40+
-- Freeze GC at GCSsweepstring phase.
41+
collectgarbage('stop')
42+
-- Create another GCcdata object.
43+
-- As a result, ctype_state->finalizer->hmask is 3 (i.e. 4 fields):
44+
-- * __mode = "k";
45+
-- * finalizer for the old (and dead) GCcdata;
46+
-- * finalizer for the new GCcdata.
47+
-- Hence, reallocation is required
48+
local new_type = ffi.metatype('struct new', { __gc = create_fin(1) })
49+
-- Reallocation occurs and the dead GCcdata is copied to the new hpart.
50+
_ = new_type(1)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/perl
2+
#
3+
# Tests for FFI machinery and related patches.
4+
# Copyright (C) 2020-2025 LuaVela Authors. See Copyright Notice in COPYRIGHT
5+
# Copyright (C) 2015-2020 IPONWEB Ltd. See Copyright Notice in COPYRIGHT
6+
7+
use 5.010;
8+
use warnings;
9+
use strict;
10+
use lib './lib';
11+
12+
use UJit::Test;
13+
14+
sub _run_tests_group {
15+
my ($extlib_func_name, $tester, @tests) = @_;
16+
17+
foreach my $test (@tests) {
18+
my %asserts = %{ $test->{asserts} };
19+
20+
my $run_result = $tester->run($test->{file},
21+
lua_args => $extlib_func_name);
22+
23+
while (my($check, $expr_list) = each %asserts) {
24+
if (scalar @{ $expr_list}) {
25+
$run_result->$check($_) for (@{ $expr_list });
26+
} else {
27+
$run_result->$check();
28+
}
29+
}
30+
}
31+
}
32+
33+
sub run_tests {
34+
my ($tester, $tests) = @_;
35+
36+
while (my ($extlib_func_name, $tests_group) = each %{ $tests }) {
37+
_run_tests_group($extlib_func_name, $tester, @{ $tests_group });
38+
}
39+
}
40+
41+
my $tester = UJit::Test->new(
42+
chunks_dir => './chunks/ffi',
43+
);
44+
45+
my @tarray = (
46+
'gcfin_table_reallocation.lua',
47+
);
48+
49+
$tester->run($_)->exit_ok() for (@tarray);
50+
51+
exit;

0 commit comments

Comments
 (0)