snow-data 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/extconf.rb +3 -0
- data/ext/snow-data/snow-data.c +147 -29
- data/lib/snow-data/c_struct.rb +145 -371
- data/lib/snow-data/c_struct/array_base.rb +164 -0
- data/lib/snow-data/c_struct/struct_base.rb +304 -0
- data/lib/snow-data/memory.rb +14 -4
- data/lib/snow-data/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de5502dc1786af224a1344f0221110e87bae22a0
|
4
|
+
data.tar.gz: 207bffe6bbe3d1bf6f6ff3bef67615b12ef3767d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c38b07ce23e8fc20c451c5d529d535e9bbe826a5e41f149d5903c29a53ac7d01024d0becda54c7e75e4c24e07f1b216fe4a87a45098394b121eb3de309bd860f
|
7
|
+
data.tar.gz: a71c448abd01932b31ec66d5bb197469b6b6521404fa270f20a45ae150eb51d798011e1fe56daea4d1421d684cf145c333582ecd6aa1d1db523f1bca01a5b5b6
|
data/ext/extconf.rb
CHANGED
@@ -19,6 +19,7 @@ option_mappings = {
|
|
19
19
|
'-Ws' => OptKVPair[:warn_implicit_size, true],
|
20
20
|
'--warn-no-bytesize' => OptKVPair[:warn_no_bytesize, true],
|
21
21
|
'-Wbs' => OptKVPair[:warn_implicit_size, true],
|
22
|
+
'--allow-alloca' => OptKVPair[:allow_alloca, true],
|
22
23
|
'--debug-memory-copy' => OptKVPair[:debug_memory_copy, true],
|
23
24
|
'--debug-allocations' => OptKVPair[:debug_allocations, true]
|
24
25
|
}
|
@@ -27,6 +28,7 @@ options = {
|
|
27
28
|
:build_debug => false,
|
28
29
|
:warn_implicit_size => false,
|
29
30
|
:warn_no_bytesize => false,
|
31
|
+
:allow_alloca => false,
|
30
32
|
:debug_memory_copy => false,
|
31
33
|
:debug_allocations => false
|
32
34
|
}
|
@@ -50,6 +52,7 @@ else
|
|
50
52
|
$stdout.puts "Building extension in release mode"
|
51
53
|
end
|
52
54
|
|
55
|
+
$CFLAGS += ' -DSD_ALLOW_ALLOCA' if options[:allow_alloca]
|
53
56
|
$CFLAGS += ' -DSD_SD_WARN_ON_IMPLICIT_COPY_SIZE' if options[:warn_implicit_size]
|
54
57
|
$CFLAGS += ' -DSD_WARN_ON_NO_BYTESIZE_METHOD' if options[:warn_no_bytesize]
|
55
58
|
$CFLAGS += ' -DSD_VERBOSE_COPY_LOG' if options[:debug_memory_copy]
|
data/ext/snow-data/snow-data.c
CHANGED
@@ -8,6 +8,11 @@
|
|
8
8
|
#include <stdint.h>
|
9
9
|
#include <stdio.h>
|
10
10
|
|
11
|
+
typedef enum e_sd_free_memory_flag {
|
12
|
+
SD_DO_NOT_FREE_MEMORY = 0,
|
13
|
+
SD_FREE_MEMORY = 1
|
14
|
+
} sd_free_memory_flag_t;
|
15
|
+
|
11
16
|
static ID kSD_IVAR_BYTESIZE;
|
12
17
|
static ID kSD_IVAR_ALIGNMENT;
|
13
18
|
static ID kSD_ID_BYTESIZE;
|
@@ -175,33 +180,54 @@ static void *align_ptr(void *ptr, size_t alignment)
|
|
175
180
|
/*
|
176
181
|
Allocated a block of memory of at least size bytes aligned to the given byte
|
177
182
|
alignment.
|
183
|
+
|
184
|
+
Raises a NoMemoryError if it's not possible to allocate memory.
|
178
185
|
*/
|
179
186
|
static void *com_malloc(size_t size, size_t alignment)
|
180
187
|
{
|
181
188
|
const size_t aligned_size = align_size(size + sizeof(void *), alignment);
|
182
189
|
void *const ptr = xcalloc(aligned_size, 1);
|
183
|
-
void **
|
190
|
+
void **aligned_ptr;
|
191
|
+
|
192
|
+
if (!ptr) {
|
193
|
+
rb_raise(rb_eNoMemError,
|
194
|
+
"Failed to allocate %zu (req: %zu) bytes via malloc",
|
195
|
+
aligned_size, size);
|
196
|
+
return NULL;
|
197
|
+
}
|
198
|
+
|
199
|
+
aligned_ptr = align_ptr((uint8_t *)ptr + sizeof(void *), alignment);
|
184
200
|
aligned_ptr[-1] = ptr;
|
201
|
+
|
185
202
|
#ifdef SD_VERBOSE_MALLOC_LOG
|
186
203
|
fprintf(stderr, "Allocated block %p with aligned size %zu (requested: %zu"
|
187
204
|
" aligned to %zu bytes), returning aligned pointer %p with usable size %td\n",
|
188
205
|
ptr, aligned_size, size, alignment, aligned_ptr,
|
189
206
|
(uint8_t *)(ptr + aligned_size) - (uint8_t *)aligned_ptr);
|
190
207
|
#endif
|
208
|
+
|
191
209
|
return aligned_ptr;
|
192
210
|
}
|
193
211
|
|
194
212
|
/*
|
195
213
|
Frees memory previously allocated by com_malloc. This _does not work_ if it
|
196
214
|
was allocated by any other means.
|
215
|
+
|
216
|
+
Raises a RuntimeError if aligned_ptr is NULL.
|
197
217
|
*/
|
198
218
|
static void com_free(void *aligned_ptr)
|
199
219
|
{
|
220
|
+
if (!aligned_ptr) {
|
221
|
+
rb_raise(rb_eRuntimeError, "Attempt to call free on NULL");
|
222
|
+
return;
|
223
|
+
}
|
224
|
+
|
200
225
|
#ifdef SD_VERBOSE_MALLOC_LOG
|
201
226
|
fprintf(stderr, "Deallocating aligned pointer %p with underlying pointer %p\n",
|
202
227
|
aligned_ptr,
|
203
228
|
((void **)aligned_ptr)[-1]);
|
204
229
|
#endif
|
230
|
+
|
205
231
|
xfree(((void **)aligned_ptr)[-1]);
|
206
232
|
}
|
207
233
|
|
@@ -1286,9 +1312,44 @@ static VALUE sd_set_string(int argc, VALUE *argv, VALUE self)
|
|
1286
1312
|
return sd_set_string_nullterm(self, sd_offset, sd_value, !!RTEST(sd_null_terminated));
|
1287
1313
|
}
|
1288
1314
|
|
1315
|
+
/*
|
1316
|
+
Frees memory associated with self regardless of whether the object is frozen.
|
1317
|
+
*/
|
1318
|
+
static void sd_memory_force_free(VALUE self)
|
1319
|
+
{
|
1320
|
+
struct RData *data = RDATA(self);
|
1321
|
+
|
1322
|
+
if (data->data && data->dfree) {
|
1323
|
+
data->dfree(data->data);
|
1324
|
+
data->dfree = 0;
|
1325
|
+
} else if (!data->data) {
|
1326
|
+
rb_raise(rb_eRuntimeError,
|
1327
|
+
"Double-free on %s",
|
1328
|
+
rb_obj_classname(self));
|
1329
|
+
}
|
1330
|
+
|
1331
|
+
data->data = 0;
|
1332
|
+
rb_ivar_set(self, kSD_IVAR_BYTESIZE, INT2FIX(0));
|
1333
|
+
}
|
1334
|
+
|
1335
|
+
/*
|
1336
|
+
Returns a Data object wrapped with the given klass and the appropriate size
|
1337
|
+
and alignment instance variables.
|
1338
|
+
*/
|
1339
|
+
static VALUE sd_wrap_memory(VALUE klass, void *data, size_t size, size_t alignment, sd_free_memory_flag_t should_free)
|
1340
|
+
{
|
1341
|
+
VALUE memory = Data_Wrap_Struct(klass, 0, (should_free ? com_free : 0), data);
|
1342
|
+
rb_ivar_set(memory, kSD_IVAR_BYTESIZE, SIZET2NUM(size));
|
1343
|
+
rb_ivar_set(memory, kSD_IVAR_ALIGNMENT, SIZET2NUM(alignment));
|
1344
|
+
rb_obj_call_init(memory, 0, 0);
|
1345
|
+
return memory;
|
1346
|
+
}
|
1347
|
+
|
1289
1348
|
/*
|
1290
1349
|
call-seq:
|
1291
1350
|
new(address, size, alignment = SIZEOF_VOID_POINTER) => Memory
|
1351
|
+
wrap(address, size, alignment = SIZEOF_VOID_POINTER) => Memory
|
1352
|
+
__wrap__(address, size, alignment = SIZEOF_VOID_POINTER) => Memory
|
1292
1353
|
|
1293
1354
|
Creates a new Memory object that wraps an existing pointer. Alignment is
|
1294
1355
|
optional and defaults to the size of a pointer (Memory::SIZEOF_VOID_POINTER).
|
@@ -1310,7 +1371,7 @@ static VALUE sd_set_string(int argc, VALUE *argv, VALUE self)
|
|
1310
1371
|
an ArgumentError to provide a NULL (zero) address.
|
1311
1372
|
|
1312
1373
|
If a subclass overrides ::new, it is also aliased as ::wrap and ::__wrap__.
|
1313
|
-
Subclasses may override ::wrap but must
|
1374
|
+
Subclasses may override ::wrap or ::new but must never override ::__wrap__.
|
1314
1375
|
*/
|
1315
1376
|
static VALUE sd_memory_new(int argc, VALUE *argv, VALUE self)
|
1316
1377
|
{
|
@@ -1346,11 +1407,7 @@ static VALUE sd_memory_new(int argc, VALUE *argv, VALUE self)
|
|
1346
1407
|
alignment = NUM2SIZET(sd_alignment);
|
1347
1408
|
}
|
1348
1409
|
|
1349
|
-
memory =
|
1350
|
-
rb_ivar_set(memory, kSD_IVAR_BYTESIZE, SIZET2NUM(size));
|
1351
|
-
rb_ivar_set(memory, kSD_IVAR_ALIGNMENT, SIZET2NUM(alignment));
|
1352
|
-
rb_obj_call_init(memory, 0, 0);
|
1353
|
-
rb_obj_taint(memory);
|
1410
|
+
memory = sd_wrap_memory(self, address, size, alignment, SD_DO_NOT_FREE_MEMORY);
|
1354
1411
|
|
1355
1412
|
return memory;
|
1356
1413
|
}
|
@@ -1358,11 +1415,16 @@ static VALUE sd_memory_new(int argc, VALUE *argv, VALUE self)
|
|
1358
1415
|
/*
|
1359
1416
|
call-seq:
|
1360
1417
|
malloc(size, alignment = nil) => Memory
|
1418
|
+
__malloc__(size, alignment = nil) => Memory
|
1361
1419
|
|
1362
1420
|
Allocates a new block with the given size and alignment and returns it. If
|
1363
1421
|
no alignment is specified, it defaults to Snow::Memory::SIZEOF_VOID_POINTER.
|
1364
1422
|
|
1365
1423
|
Raises a RangeError if either size is zero or alignment is not a power of two.
|
1424
|
+
|
1425
|
+
If a subclass overrides ::malloc, which is a bad idea and should not be done,
|
1426
|
+
then it is aliased as ::__malloc__ as well. Subclasses must never override
|
1427
|
+
::__malloc__.
|
1366
1428
|
*/
|
1367
1429
|
static VALUE sd_memory_malloc(int argc, VALUE *argv, VALUE self)
|
1368
1430
|
{
|
@@ -1388,15 +1450,82 @@ static VALUE sd_memory_malloc(int argc, VALUE *argv, VALUE self)
|
|
1388
1450
|
|
1389
1451
|
/* Allocate block */
|
1390
1452
|
data = com_malloc(size, alignment);
|
1391
|
-
memory
|
1453
|
+
memory = sd_wrap_memory(self, data, size, alignment, SD_FREE_MEMORY);
|
1392
1454
|
|
1393
|
-
rb_ivar_set(memory, kSD_IVAR_BYTESIZE, SIZET2NUM(size));
|
1394
|
-
rb_ivar_set(memory, kSD_IVAR_ALIGNMENT, SIZET2NUM(alignment));
|
1395
|
-
rb_obj_call_init(memory, 0, 0);
|
1396
|
-
rb_obj_taint(memory);
|
1397
1455
|
return memory;
|
1398
1456
|
}
|
1399
1457
|
|
1458
|
+
#ifdef SD_ALLOW_ALLOCA
|
1459
|
+
/*
|
1460
|
+
call-seq:
|
1461
|
+
alloca(size) { |memory| ... } => result of block
|
1462
|
+
__alloca__(size) { |memory| ... } => result of block
|
1463
|
+
|
1464
|
+
Allocates size bytes on the stack and yields it to a block before for use,
|
1465
|
+
then returns the result of the block. Outside of the block, any use of the
|
1466
|
+
yielded memory's address is considered undefined behavior and may lead to
|
1467
|
+
crashes or worse. The yielded memory is only valid for the duration of the
|
1468
|
+
block. If you want to preserve the memory after use, either duplicate the
|
1469
|
+
memory block or realloc! the memory, both of which will copy it from the stack
|
1470
|
+
to the heap.
|
1471
|
+
|
1472
|
+
The alignment of the yielded memory is always the size of a pointer.
|
1473
|
+
|
1474
|
+
Because of how downright evil alloca is in some situations, it is disabled by
|
1475
|
+
default. You must explicitly install snow-data with the --allow-alloca flag to
|
1476
|
+
use this function. If you're not certain whether alloca is available for a
|
1477
|
+
particular installation, then you should check if the Memory class responds to
|
1478
|
+
it first.
|
1479
|
+
|
1480
|
+
if Snow::Memory.respond_to?(:__alloca__)
|
1481
|
+
Snow::Memory.__alloca__(64) { |mem|
|
1482
|
+
# ...
|
1483
|
+
}
|
1484
|
+
end
|
1485
|
+
|
1486
|
+
Subclasses may override alloca to provide a predefined size argument, but
|
1487
|
+
subclasses must never override __alloca__. It is considered bad form to
|
1488
|
+
implement __alloca__ using heap allocation for installations that don't
|
1489
|
+
provide it.
|
1490
|
+
*/
|
1491
|
+
static VALUE sd_memory_alloca(VALUE self, VALUE sd_size)
|
1492
|
+
{
|
1493
|
+
VALUE result = Qnil;
|
1494
|
+
void *stack_memory = NULL;
|
1495
|
+
struct RData *block_data;
|
1496
|
+
size_t size = NUM2SIZET(sd_size);
|
1497
|
+
VALUE block;
|
1498
|
+
|
1499
|
+
rb_need_block();
|
1500
|
+
|
1501
|
+
if (size == 0) {
|
1502
|
+
rb_raise(rb_eRangeError, "Size of block must be 1 or more -- zero-byte"
|
1503
|
+
" blocks are not permitted.");
|
1504
|
+
}
|
1505
|
+
|
1506
|
+
stack_memory = alloca(size);
|
1507
|
+
if (stack_memory == NULL) {
|
1508
|
+
rb_raise(rb_eNoMemError,
|
1509
|
+
"Failed to allocated %zu bytes via alloca",
|
1510
|
+
size);
|
1511
|
+
}
|
1512
|
+
|
1513
|
+
block = sd_wrap_memory(self, stack_memory, size, SIZEOF_VOIDP, SD_DO_NOT_FREE_MEMORY);
|
1514
|
+
block_data = RDATA(block);
|
1515
|
+
result = rb_yield(block);
|
1516
|
+
|
1517
|
+
/*
|
1518
|
+
If the block hasn't been realloc!'d or freed, free it now if it hasn't been
|
1519
|
+
frozen for some reason.
|
1520
|
+
*/
|
1521
|
+
if (block_data->data == stack_memory && !block_data->dfree && !OBJ_FROZEN(block)) {
|
1522
|
+
sd_memory_force_free(block);
|
1523
|
+
}
|
1524
|
+
|
1525
|
+
return result;
|
1526
|
+
}
|
1527
|
+
#endif
|
1528
|
+
|
1400
1529
|
/*
|
1401
1530
|
call-seq:
|
1402
1531
|
realloc!(size, alignment = nil) => self
|
@@ -1488,22 +1617,8 @@ static VALUE sd_memory_realloc(int argc, VALUE *argv, VALUE self)
|
|
1488
1617
|
*/
|
1489
1618
|
static VALUE sd_memory_free(VALUE self)
|
1490
1619
|
{
|
1491
|
-
struct RData *data = RDATA(self);
|
1492
|
-
|
1493
1620
|
rb_check_frozen(self);
|
1494
|
-
|
1495
|
-
if (data->data && data->dfree) {
|
1496
|
-
data->dfree(data->data);
|
1497
|
-
data->dfree = 0;
|
1498
|
-
} else if (!data->data) {
|
1499
|
-
rb_raise(rb_eRuntimeError,
|
1500
|
-
"Double-free on %s",
|
1501
|
-
rb_obj_classname(self));
|
1502
|
-
}
|
1503
|
-
|
1504
|
-
data->data = 0;
|
1505
|
-
rb_ivar_set(self, kSD_IVAR_BYTESIZE, INT2FIX(0));
|
1506
|
-
|
1621
|
+
sd_memory_force_free(self);
|
1507
1622
|
return self;
|
1508
1623
|
}
|
1509
1624
|
|
@@ -1804,8 +1919,11 @@ void Init_snowdata_bindings(void)
|
|
1804
1919
|
rb_const_set(sd_memory_klass, rb_intern("SIZEOF_UINTPTR_T"), SIZET2NUM(SIZEOF_UINTPTR_T));
|
1805
1920
|
rb_const_set(sd_memory_klass, rb_intern("SIZEOF_VOID_POINTER"), SIZET2NUM(sizeof(void *)));
|
1806
1921
|
|
1807
|
-
rb_define_singleton_method(sd_memory_klass, "
|
1808
|
-
rb_define_singleton_method(sd_memory_klass, "
|
1922
|
+
rb_define_singleton_method(sd_memory_klass, "__wrap__", sd_memory_new, -1);
|
1923
|
+
rb_define_singleton_method(sd_memory_klass, "__malloc__", sd_memory_malloc, -1);
|
1924
|
+
#ifdef SD_ALLOW_ALLOCA
|
1925
|
+
rb_define_singleton_method(sd_memory_klass, "__alloca__", sd_memory_alloca, 1);
|
1926
|
+
#endif
|
1809
1927
|
rb_define_singleton_method(sd_memory_klass, "align_size", sd_align_size, -1);
|
1810
1928
|
rb_define_method(sd_memory_klass, "realloc!", sd_memory_realloc, -1);
|
1811
1929
|
rb_define_method(sd_memory_klass, "copy!", sd_memory_copy, -1);
|
data/lib/snow-data/c_struct.rb
CHANGED
@@ -4,6 +4,8 @@
|
|
4
4
|
|
5
5
|
require 'snow-data/snowdata_bindings'
|
6
6
|
require 'snow-data/memory'
|
7
|
+
require 'snow-data/c_struct/struct_base'
|
8
|
+
require 'snow-data/c_struct/array_base'
|
7
9
|
|
8
10
|
module Snow
|
9
11
|
|
@@ -17,186 +19,42 @@ class CStruct
|
|
17
19
|
StructMemberInfo = Struct.new(:name, :type, :size, :length, :alignment, :offset)
|
18
20
|
|
19
21
|
|
20
|
-
|
21
22
|
#
|
22
|
-
#
|
23
|
-
#
|
23
|
+
# Whether long inspect strings are enabled. See both ::long_inspect= and
|
24
|
+
# ::long_inspect for accessors.
|
24
25
|
#
|
25
|
-
|
26
|
-
|
27
|
-
@@long_inspect = false
|
28
|
-
|
29
|
-
#
|
30
|
-
# Whether long inspect strings are enabled. By default, they are disabled.
|
31
|
-
#
|
32
|
-
# Long inspect strings can be useful for debugging, sepcially if you want to
|
33
|
-
# see the value, length, and alignment of every struct member in inspect
|
34
|
-
# strings. Otherwise, you can safely leave this disabled.
|
35
|
-
#
|
36
|
-
def self.long_inspect=(enabled)
|
37
|
-
@@long_inspect = !!enabled
|
38
|
-
end
|
39
|
-
|
40
|
-
|
41
|
-
def self.long_inspect
|
42
|
-
@@long_inspect
|
43
|
-
end
|
44
|
-
|
45
|
-
|
46
|
-
#
|
47
|
-
# Returns the offset of a member.
|
48
|
-
#
|
49
|
-
def self.offset_of(member)
|
50
|
-
self.MEMBERS_HASH[member].offset
|
51
|
-
end
|
52
|
-
|
53
|
-
|
54
|
-
#
|
55
|
-
# Returns the type name of a member.
|
56
|
-
#
|
57
|
-
def self.type_of(member)
|
58
|
-
self.MEMBERS_HASH[member].type
|
59
|
-
end
|
60
|
-
|
61
|
-
|
62
|
-
#
|
63
|
-
# Returns the size in bytes of a member.
|
64
|
-
#
|
65
|
-
def self.bytesize_of(member)
|
66
|
-
self.MEMBERS_HASH[member].size
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
|
-
#
|
71
|
-
# Returns the alignment of a member.
|
72
|
-
#
|
73
|
-
def self.alignment_of(member)
|
74
|
-
self.MEMBERS_HASH[member].alignment
|
75
|
-
end
|
76
|
-
|
77
|
-
|
78
|
-
#
|
79
|
-
# Returns the length of a member.
|
80
|
-
#
|
81
|
-
def self.length_of(member)
|
82
|
-
self.MEMBERS_HASH[member].length
|
83
|
-
end
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
#
|
88
|
-
# Returns the address of a member. This address is only valid for the
|
89
|
-
# receiver.
|
90
|
-
#
|
91
|
-
def address_of(member)
|
92
|
-
self.address + self.class.offset_of(member)
|
93
|
-
end
|
94
|
-
|
95
|
-
|
96
|
-
#
|
97
|
-
# Returns the offset of a member.
|
98
|
-
#
|
99
|
-
def offset_of(member)
|
100
|
-
self.class.offset_of(member)
|
101
|
-
end
|
102
|
-
|
103
|
-
|
104
|
-
#
|
105
|
-
# Returns the size in bytes of a member.
|
106
|
-
#
|
107
|
-
def bytesize_of(member)
|
108
|
-
self.class.bytesize_of(member)
|
109
|
-
end
|
110
|
-
|
111
|
-
|
112
|
-
#
|
113
|
-
# Returns the alignment of a member.
|
114
|
-
#
|
115
|
-
def alignment_of(member)
|
116
|
-
self.class.alignment_of(member)
|
117
|
-
end
|
118
|
-
|
119
|
-
|
120
|
-
#
|
121
|
-
# Returns the length of a member.
|
122
|
-
#
|
123
|
-
def length_of(member)
|
124
|
-
self.class.length_of(member)
|
125
|
-
end
|
126
|
-
|
127
|
-
|
128
|
-
#
|
129
|
-
# Returns the type name of a member.
|
130
|
-
#
|
131
|
-
def type_of(member)
|
132
|
-
self.class.type_of(member)
|
133
|
-
end
|
134
|
-
|
135
|
-
|
136
|
-
#
|
137
|
-
# Returns a hash of all of the struct's member names to their values.
|
138
|
-
#
|
139
|
-
def to_h
|
140
|
-
self.class::MEMBERS.inject({}) do |hash, member|
|
141
|
-
length = member.length
|
142
|
-
name = member.name
|
143
|
-
|
144
|
-
hash[name] = if length > 1
|
145
|
-
(0 ... length).map { |index| __send__(name, index) }
|
146
|
-
else
|
147
|
-
__send__(name)
|
148
|
-
end
|
149
|
-
|
150
|
-
hash
|
151
|
-
end
|
152
|
-
end
|
26
|
+
@@long_inspect = false
|
153
27
|
|
154
28
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
type = member.type
|
169
|
-
align = member.alignment
|
170
|
-
length = member.length
|
171
|
-
length_decl = length > 1 ? "[#{length}]" : ''
|
172
|
-
|
173
|
-
values_text = if length > 1
|
174
|
-
single_member_text = (0 ... length).map { |index|
|
175
|
-
"[#{ index }]=#{ self.__send__(member.name, index).inspect }"
|
176
|
-
}.join(', ')
|
177
|
-
|
178
|
-
"{ #{ single_member_text } }"
|
179
|
-
else
|
180
|
-
self.__send__(member.name).inspect
|
181
|
-
end
|
182
|
-
|
183
|
-
"#{ name }:#{ type }#{ length_decl }:#{ align }=#{ values_text }"
|
184
|
-
|
185
|
-
end).join('; ')
|
186
|
-
|
187
|
-
" #{all_members_text}"
|
188
|
-
else # member_text = if ...
|
189
|
-
# Skip members
|
190
|
-
''
|
191
|
-
end # member_text = if ...
|
192
|
-
|
193
|
-
"<#{ self.class }:0x#{ id_text } *0x#{ addr_text }:#{ self.bytesize }:#{ self.alignment }#{member_text}>"
|
194
|
-
end
|
29
|
+
#
|
30
|
+
# call-seq:
|
31
|
+
# long_inspect = boolean => boolean
|
32
|
+
#
|
33
|
+
# Sets whether long inspect strings are enabled. By default, they are disabled.
|
34
|
+
#
|
35
|
+
# Long inspect strings can be useful for debugging, sepcially if you want to
|
36
|
+
# see the value, length, and alignment of every struct member in inspect
|
37
|
+
# strings. Otherwise, you can safely leave this disabled.
|
38
|
+
#
|
39
|
+
def self.long_inspect=(enabled)
|
40
|
+
@@long_inspect = !!enabled
|
41
|
+
end
|
195
42
|
|
196
43
|
|
197
|
-
|
44
|
+
#
|
45
|
+
# call-seq:
|
46
|
+
# long_inspect => boolean
|
47
|
+
#
|
48
|
+
# Returns whether long_inspect is enabled. By default, it is disabled.
|
49
|
+
#
|
50
|
+
def self.long_inspect
|
51
|
+
@@long_inspect
|
52
|
+
end
|
198
53
|
|
199
54
|
|
55
|
+
#
|
56
|
+
# call-seq:
|
57
|
+
# power_of_two?(num) => boolean
|
200
58
|
#
|
201
59
|
# Returns whether num is a power of two and nonzero.
|
202
60
|
#
|
@@ -205,6 +63,9 @@ class CStruct
|
|
205
63
|
end
|
206
64
|
|
207
65
|
|
66
|
+
#
|
67
|
+
# call-seq:
|
68
|
+
# member_encoding(name, type, length: 1, alignment: nil) => String
|
208
69
|
#
|
209
70
|
# Returns an encoding for a struct member with the given name, type,
|
210
71
|
# length, and alignment. The type must be a string or symbol, not a Class or
|
@@ -228,6 +89,9 @@ class CStruct
|
|
228
89
|
#--
|
229
90
|
# Ordinarily I'd write a lexer for this sort of thing, but regex actually
|
230
91
|
# seems to work fine.
|
92
|
+
#
|
93
|
+
# TODO: At any rate, replace this with a lexer/parser. At least that way it'll
|
94
|
+
# be possible to provide validation for encodings.
|
231
95
|
#++
|
232
96
|
ENCODING_REGEX = %r{
|
233
97
|
(?<name> # 0
|
@@ -247,6 +111,7 @@ class CStruct
|
|
247
111
|
\s* (?: ; | $ | \n) # terminator
|
248
112
|
}mx
|
249
113
|
|
114
|
+
|
250
115
|
# Alignemnts for default types.
|
251
116
|
ALIGNMENTS = {
|
252
117
|
:char => 1,
|
@@ -276,6 +141,7 @@ class CStruct
|
|
276
141
|
:uintptr_t => Memory::SIZEOF_UINTPTR_T
|
277
142
|
}
|
278
143
|
|
144
|
+
|
279
145
|
# Sizes of default types.
|
280
146
|
SIZES = {
|
281
147
|
:char => 1,
|
@@ -305,6 +171,7 @@ class CStruct
|
|
305
171
|
:uintptr_t => Memory::SIZEOF_UINTPTR_T
|
306
172
|
}
|
307
173
|
|
174
|
+
|
308
175
|
# Used for getters/setters on Memory objects. Simply maps short type names to
|
309
176
|
# their long-form type names.
|
310
177
|
LONG_NAMES = {
|
@@ -488,233 +355,140 @@ class CStruct
|
|
488
355
|
# `fetch(index)` and `store(index, value)` methods, both aliased to `[]` and
|
489
356
|
# `[]=` respectively.
|
490
357
|
#
|
491
|
-
def self.new(
|
492
|
-
encoding, klass_name = case args.length
|
493
|
-
when 1 then args
|
494
|
-
when 2 then args.reverse
|
495
|
-
else
|
496
|
-
raise ArgumentError, "Invalid arguments to CStruct::new"
|
497
|
-
end
|
498
|
-
|
358
|
+
def self.new(klass_name = nil, encoding)
|
499
359
|
klass_name = klass_name.intern if klass_name
|
500
360
|
|
501
|
-
members =
|
502
|
-
|
503
|
-
encoding.scan(ENCODING_REGEX) do
|
504
|
-
|match|
|
505
|
-
name = match[0].intern
|
506
|
-
type = match[1].intern
|
507
|
-
type = LONG_NAMES[type] if LONG_NAMES.include?(type)
|
508
|
-
length = (match[3] || 1).to_i
|
509
|
-
align = (match[5] || ALIGNMENTS[type] || 1).to_i
|
510
|
-
offset = 0
|
511
|
-
|
512
|
-
last_type = members.last
|
513
|
-
if last_type
|
514
|
-
offset += Memory.align_size(last_type.offset + last_type.size, align)
|
515
|
-
end
|
516
|
-
|
517
|
-
members << StructMemberInfo[name, type, SIZES[type] * length, length, align, offset].freeze
|
518
|
-
end
|
361
|
+
members = decode_member_info(encoding)
|
519
362
|
|
520
363
|
raise "No valid members found in encoding" if members.empty?
|
521
364
|
|
522
|
-
|
523
|
-
size = members.last.size + members.last.offset
|
524
|
-
aligned_size = Memory.align_size(size, alignment)
|
525
|
-
|
526
|
-
members.freeze
|
527
|
-
|
528
|
-
klass = Class.new(StructBase) do |struct_klass|
|
529
|
-
const_set(:ENCODING, String.new(encoding).freeze)
|
530
|
-
const_set(:MEMBERS, members)
|
531
|
-
const_set(:SIZE, size)
|
532
|
-
const_set(:ALIGNED_SIZE, aligned_size)
|
533
|
-
const_set(:ALIGNMENT, alignment)
|
534
|
-
const_set(:MEMBERS_HASH, members.reduce({}) { |offs, member| offs[member.name] = member ; offs })
|
535
|
-
|
536
|
-
def self.new(&block)
|
537
|
-
inst = __malloc__(self::SIZE, self::ALIGNMENT)
|
538
|
-
yield(inst) if block_given?
|
539
|
-
inst
|
540
|
-
end
|
541
|
-
|
542
|
-
self::MEMBERS.each do
|
543
|
-
|member|
|
544
|
-
|
545
|
-
name = member.name
|
546
|
-
index_range = (0...member.length)
|
547
|
-
type_name = member.type
|
548
|
-
type_size = ::Snow::CStruct::SIZES[member.type]
|
549
|
-
offset = member.offset
|
550
|
-
getter = :"get_#{type_name}"
|
551
|
-
setter = :"set_#{type_name}"
|
552
|
-
get_name = :"get_#{name}"
|
553
|
-
set_name = :"set_#{name}"
|
554
|
-
|
555
|
-
define_method(get_name) do |index = 0|
|
556
|
-
if index === index_range
|
557
|
-
raise RangeError, "Index #{index} for #{name} is out of range: must be in #{index_range}"
|
558
|
-
end
|
559
|
-
off = offset + index * type_size
|
560
|
-
__send__(getter, off)
|
561
|
-
end # get_name
|
562
|
-
|
563
|
-
|
564
|
-
define_method(set_name) do |value, index = 0|
|
565
|
-
if index === index_range
|
566
|
-
raise RangeError, "Index #{index} for #{name} is out of range: must be in #{index_range}"
|
567
|
-
end
|
568
|
-
off = offset + index * type_size
|
569
|
-
__send__(setter, off, value)
|
570
|
-
value
|
571
|
-
end # set_name
|
572
|
-
|
573
|
-
|
574
|
-
alias_method :"#{name}", get_name
|
575
|
-
alias_method :"#{name}=", set_name
|
576
|
-
|
577
|
-
end # define member get / set methods
|
578
|
-
|
579
|
-
|
580
|
-
# Array inner class (not a subclass of StructBase because it has no members itself)
|
581
|
-
const_set(:Array, Class.new(Memory) do |array_klass|
|
582
|
-
|
583
|
-
const_set(:BASE, struct_klass)
|
584
|
-
|
585
|
-
# The length of the array.
|
586
|
-
attr_reader :length
|
587
|
-
|
588
|
-
|
589
|
-
include Enumerable
|
590
|
-
|
591
|
-
|
592
|
-
def self.wrap(address, length_in_elements) # :nodoc:
|
593
|
-
__wrap__(address, length_in_elements * self::BASE::SIZE)
|
594
|
-
end
|
595
|
-
|
596
|
-
|
597
|
-
def self.new(length) # :nodoc:
|
598
|
-
length = length.to_i
|
599
|
-
raise ArgumentError, "Length must be greater than zero" if length < 1
|
600
|
-
inst = __malloc__(length * self::BASE::SIZE, self::BASE::ALIGNMENT)
|
601
|
-
inst.instance_variable_set(:@length, length)
|
602
|
-
inst.instance_variable_set(:@__cache__, nil)
|
603
|
-
inst
|
604
|
-
end
|
605
|
-
|
606
|
-
|
607
|
-
def resize!(new_length) # :nodoc:
|
608
|
-
raise ArgumentError, "Length must be greater than zero" if new_length < 1
|
609
|
-
realloc!(new_length * self.class::BASE::SIZE, self.class::BASE::ALIGNMENT)
|
610
|
-
@length = new_length
|
611
|
-
__free_cache__
|
612
|
-
self
|
613
|
-
end
|
614
|
-
|
615
|
-
|
616
|
-
def each(&block) # :nodoc:
|
617
|
-
return to_enum(:each) unless block_given?
|
618
|
-
(0 ... self.length).each { |index| yield fetch(index) }
|
619
|
-
self
|
620
|
-
end
|
365
|
+
klass = build_struct_type(members)
|
621
366
|
|
367
|
+
if klass_name
|
368
|
+
const_set(klass_name, klass)
|
369
|
+
add_type(klass_name, klass)
|
370
|
+
end
|
622
371
|
|
623
|
-
|
624
|
-
|
625
|
-
self.dup.map!(&block)
|
626
|
-
end
|
372
|
+
klass
|
373
|
+
end
|
627
374
|
|
628
375
|
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
376
|
+
#
|
377
|
+
# Decodes an encoding string and returns an array of StructMemberInfo objects
|
378
|
+
# describing the members of a struct for the given encoding. You may then pass
|
379
|
+
# this array to build_struct_type to create a new struct class or
|
380
|
+
# encode_member_info to get an encoding string for the encoding string you
|
381
|
+
# just decoded, as though that were useful to you somehow.
|
382
|
+
#
|
383
|
+
def self.decode_member_info(encoding)
|
384
|
+
total_size = 0
|
385
|
+
encoding.scan(ENCODING_REGEX).map do
|
386
|
+
|match|
|
387
|
+
name = match[0].intern
|
388
|
+
type = match[1].intern
|
389
|
+
type = LONG_NAMES[type] if LONG_NAMES.include?(type)
|
390
|
+
length = (match[3] || 1).to_i
|
391
|
+
align = (match[5] || ALIGNMENTS[type] || 1).to_i
|
392
|
+
size = SIZES[type] * length
|
393
|
+
offset = Memory.align_size(total_size, align)
|
394
|
+
total_size = offset + size
|
395
|
+
|
396
|
+
StructMemberInfo[name, type, size, length, align, offset]
|
397
|
+
end
|
398
|
+
end
|
634
399
|
|
635
400
|
|
636
|
-
|
637
|
-
|
638
|
-
|
401
|
+
#
|
402
|
+
# Given an array of StructMemberInfo objects, returns a valid encoding string
|
403
|
+
# for those objects in the order they're specified in the array. The info
|
404
|
+
# objects' offsets are ignored, as these cannot be specified using an encoding
|
405
|
+
# string.
|
406
|
+
#
|
407
|
+
def self.encode_member_info(members)
|
408
|
+
members.map { |member|
|
409
|
+
"#{member.name}:#{member.type}[#{member.length}]:#{member.alignment}"
|
410
|
+
}.join(?;)
|
411
|
+
end
|
639
412
|
|
640
413
|
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
#
|
651
|
-
# You can use this to assign _any_ Data subclass to an array value, but
|
652
|
-
# keep in mind that the data assigned MUST -- again, MUST -- be at least
|
653
|
-
# as large as the array's base struct type in bytes or the assigned
|
654
|
-
# data object MUST respond to a bytesize message to get its size in
|
655
|
-
# bytes.
|
656
|
-
#
|
657
|
-
def store(index, data) # :nodoc:
|
658
|
-
raise RuntimeError, "Attempt to access deallocated array" if @length == 0
|
659
|
-
raise TypeError, "Invalid value type, must be Data, but got #{data.class}" if ! data.kind_of?(Data)
|
660
|
-
raise RangeError, "Attempt to access out-of-bounds index in #{self.class}" if index < 0 || @length <= index
|
661
|
-
@__cache__[index].copy!(data)
|
662
|
-
data
|
663
|
-
end
|
664
|
-
alias_method :[]=, :store
|
414
|
+
#
|
415
|
+
# call-seq:
|
416
|
+
# build_struct_type(members) => Class
|
417
|
+
#
|
418
|
+
# Builds a struct type for the given array of StructMemberInfo objects. The
|
419
|
+
# array of member objects must not be empty.
|
420
|
+
#
|
421
|
+
def self.build_struct_type(members)
|
422
|
+
raise ArgumentError, "Members array must not be empty" if members.empty?
|
665
423
|
|
424
|
+
# Make a copy of the members array so we can store a frozen version of it
|
425
|
+
# in the new struct class.
|
426
|
+
members = Marshal.load(Marshal.dump(members))
|
427
|
+
members.map! { |info| info.freeze }
|
428
|
+
members.freeze
|
666
429
|
|
430
|
+
# Get the alignment, size, aligned size, and encoding of the struct.
|
431
|
+
alignment = members.map { |member| member.alignment }.max { |lhs, rhs| lhs <=> rhs }
|
432
|
+
size = members.last.size + members.last.offset
|
433
|
+
aligned_size = Memory.align_size(size, alignment)
|
434
|
+
# Oddly enough, it would be easier to pass the encoding string into this
|
435
|
+
# function, but then it would ruin the nice little thing I have going where
|
436
|
+
# this function isn't dependent on parsing encodings, so we reproduce the
|
437
|
+
# encoding here as though it wasn't sitting just above us in the stack
|
438
|
+
# (which it might not be, but the chance of it is slim to none).
|
439
|
+
encoding = encode_member_info(members).freeze
|
440
|
+
|
441
|
+
Class.new(Memory) do |struct_klass|
|
442
|
+
# Set the class's constants, then include StructBase to define its members
|
443
|
+
# and other methods.
|
444
|
+
const_set(:ENCODING, encoding)
|
445
|
+
const_set(:MEMBERS, members)
|
446
|
+
const_set(:SIZE, size)
|
447
|
+
const_set(:ALIGNED_SIZE, aligned_size)
|
448
|
+
const_set(:ALIGNMENT, alignment)
|
667
449
|
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
end
|
450
|
+
const_set(:MEMBERS_HASH, members.reduce({}) { |hash, member|
|
451
|
+
hash[member.name] = member
|
452
|
+
hash
|
453
|
+
})
|
673
454
|
|
455
|
+
const_set(:MEMBERS_GETFN, members.reduce({}) { |hash, member|
|
456
|
+
hash[member.name] = :"get_#{member.name}"
|
457
|
+
hash
|
458
|
+
})
|
674
459
|
|
675
|
-
|
460
|
+
const_set(:MEMBERS_SETFN, members.reduce({}) { |hash, member|
|
461
|
+
hash[member.name] = :"set_#{member.name}"
|
462
|
+
hash
|
463
|
+
})
|
676
464
|
|
677
|
-
def __free_cache__ # :nodoc:
|
678
|
-
if @__cache__
|
679
|
-
@__cache__.each { |entry|
|
680
|
-
entry.free!
|
681
|
-
entry.remove_instance_variable(:@__base_memory__)
|
682
|
-
} # zeroes address, making it NULL
|
683
|
-
@__cache__ = nil
|
684
|
-
end
|
685
|
-
end
|
686
465
|
|
466
|
+
private :realloc!
|
687
467
|
|
688
|
-
|
689
|
-
addr = self.address
|
690
|
-
@__cache__ = (0...length).map { |index|
|
691
|
-
wrapper = self.class::BASE.__wrap__(addr + index * self.class::BASE::SIZE, self.class::BASE::SIZE)
|
692
|
-
# Make sure the wrapped object keeps the memory from being collected while it's in use
|
693
|
-
wrapper.instance_variable_set(:@__base_memory__, self)
|
694
|
-
wrapper
|
695
|
-
}
|
696
|
-
end
|
468
|
+
include StructBase
|
697
469
|
|
470
|
+
# Build and define the struct type's array class.
|
471
|
+
const_set(:Array, CStruct.build_array_type(self))
|
472
|
+
end
|
698
473
|
|
699
|
-
|
700
|
-
alias_method :[], :new
|
701
|
-
end
|
474
|
+
end
|
702
475
|
|
703
|
-
end)
|
704
476
|
|
705
|
-
|
706
|
-
|
707
|
-
|
477
|
+
#
|
478
|
+
# :nodoc:
|
479
|
+
# Generates an array class for the given struct class. This is called by
|
480
|
+
# ::build_struct_type and so shouldn't be called manually.
|
481
|
+
#
|
482
|
+
def self.build_array_type(struct_klass)
|
483
|
+
Class.new(Memory) do |array_klass|
|
484
|
+
const_set(:BASE, struct_klass)
|
708
485
|
|
709
|
-
|
486
|
+
private :realloc!
|
710
487
|
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
end
|
488
|
+
include StructArrayBase
|
489
|
+
end # Class.new
|
490
|
+
end # build_array_type
|
715
491
|
|
716
|
-
klass
|
717
|
-
end
|
718
492
|
|
719
493
|
class <<self ; alias_method :[], :new ; end
|
720
494
|
|