1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use crate::rubysys::{
libc::uintptr_t,
types::{c_int, size_t},
};
// `struct st_table`, Ruby's hash table (`ruby/st.h`); only used through
// pointers.
#[repr(C)]
pub struct StTable {
_private: [u8; 0],
}
// st_data_t: an `unsigned long`, or `unsigned long long` where that is the
// size of a pointer (64-bit Windows).
pub type StData = uintptr_t;
// st_index_t: the same size as `st_data_t`.
pub type StIndex = StData;
// `struct st_hash_type`: how a table compares and hashes its keys.
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct StHashType {
// Returns 0 when the keys are equal.
pub compare: extern "C" fn(StData, StData) -> c_int,
pub hash: extern "C" fn(StData) -> StIndex,
}
#[cfg_attr(rutie_dllimport, link(name = "rutie_ruby"))]
extern "C" {
// st_table *
// rb_st_init_numtable(void)
//
// An empty table keyed by integers (or pointers); free it with
// `rb_st_free_table`.
pub fn rb_st_init_numtable() -> *mut StTable;
// int
// rb_st_insert(st_table *, st_data_t, st_data_t)
//
// Returns 1 when the key already existed (its value is replaced), else 0.
pub fn rb_st_insert(table: *mut StTable, key: StData, value: StData) -> c_int;
// void
// rb_st_free_table(st_table *)
pub fn rb_st_free_table(table: *mut StTable);
// size_t
// rb_st_table_size(const struct st_table *tbl)
//
// The number of entries in `tbl` (`st_table_size`).
#[cfg(ruby_gte_3_2)]
pub fn rb_st_table_size(table: *const StTable) -> size_t;
// st_table *
// rb_st_init_existing_table_with_size(st_table *tab, const struct st_hash_type *type, st_index_t size)
//
// Ruby 3.3 only (3.4 removed it): initializes the `st_table` at `tab`
// (memory the caller owns, such as one allocated with `ruby_xmalloc`
// that `rb_st_free_table` may then free) as an empty table for about
// `size` entries; returns `tab`.
#[cfg(ruby_3_3)]
pub fn rb_st_init_existing_table_with_size(
table: *mut StTable,
hash_type: *const StHashType,
size: StIndex,
) -> *mut StTable;
// st_table *
// rb_st_replace(st_table *new_tab, st_table *old_tab)
//
// Ruby 3.3 only (3.4 removed it): makes the `st_table` at `new_tab` a
// copy of `old_tab`, overwriting it without freeing what it held;
// returns `new_tab`.
#[cfg(ruby_3_3)]
pub fn rb_st_replace(new_table: *mut StTable, old_table: *mut StTable) -> *mut StTable;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_st_numtable() {
crate::on_ruby_thread(|| unsafe {
let table = rb_st_init_numtable();
assert_eq!(rb_st_insert(table, 1, 10), 0);
assert_eq!(rb_st_insert(table, 2, 20), 0);
assert_eq!(rb_st_insert(table, 1, 11), 1);
#[cfg(ruby_gte_3_2)]
assert_eq!(rb_st_table_size(table), 2);
rb_st_free_table(table);
});
}
// `st_table` is at most eight words (seven on 64-bit).
#[cfg(ruby_3_3)]
const TABLE_SIZE: usize = 8 * std::mem::size_of::<usize>();
#[cfg(ruby_3_3)]
extern "C" fn compare(left: StData, right: StData) -> c_int {
(left != right) as c_int
}
#[cfg(ruby_3_3)]
extern "C" fn hash(key: StData) -> StIndex {
key
}
#[cfg(ruby_3_3)]
#[test]
fn test_st_existing_tables() {
use crate::rubysys::gc::ruby_xmalloc;
static TYPE: StHashType = StHashType { compare, hash };
crate::on_ruby_thread(|| unsafe {
let table = ruby_xmalloc(TABLE_SIZE) as *mut StTable;
assert_eq!(rb_st_init_existing_table_with_size(table, &TYPE, 4), table);
assert_eq!(rb_st_table_size(table), 0);
assert_eq!(rb_st_insert(table, 7, 70), 0);
assert_eq!(rb_st_insert(table, 7, 71), 1);
let copy = ruby_xmalloc(TABLE_SIZE) as *mut StTable;
assert_eq!(rb_st_replace(copy, table), copy);
assert_eq!(rb_st_table_size(copy), 1);
assert_eq!(rb_st_insert(copy, 8, 80), 0);
assert_eq!((rb_st_table_size(table), rb_st_table_size(copy)), (1, 2));
rb_st_free_table(table);
rb_st_free_table(copy);
});
}
}