snow-data 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|