pnmatrix 1.2.4 → 2.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: be16fe762b002a63a7d2542fb5eb50ed02cf9fd86bc9bb783a480dbeee9aca8a
4
- data.tar.gz: c0427a16637c1ed803ce4249d238ef419a903340f81e9705d59f99cd376697ad
3
+ metadata.gz: a13074d0b3e5e2bbee2a456f42fe5d5d7a4b4e581e2ae42e149d74f4a1452211
4
+ data.tar.gz: 917446ac40ebe45b13a62c6c0e1e927227beee199a411e2c0cc1fa83f5e9f1bc
5
5
  SHA512:
6
- metadata.gz: 215cd7e47e75a13afb836e630a61d2f34057452601ca7cd9a70dd9a216258cd0b0763d6c064e36754475833c0ff8cec0144bdedf36f774d65a6498ae705c4954
7
- data.tar.gz: b6f3078f05c24952b3dc8bab939a9b5f63cd673bda80450c5e229cc909913382d71adb5e9ff361a293c7e762ea741d0bc78d509567fc52fcdd1b13bb840e5670
6
+ metadata.gz: 697596b71a0d302a785d4fe503676fe89392b60e84354fba5151b873f2bbf570e11f6086972c30bdf610eb9a2621387b5bf469c82280e8586888241773a3668f
7
+ data.tar.gz: 2a42bf5f5e4c97748e6d44aa631086e94c18bdb0b6f56503ce236d97a57cb8da0a272a016686b88d656f67f86d82124f9bef5bb7fbbfe39b91deefd6c657810d
@@ -54,13 +54,13 @@ $srcs = basenames.map { |b| "#{b}.cpp" }
54
54
  create_conf_h("nmatrix_config.h")
55
55
  create_makefile("nmatrix")
56
56
 
57
- Dir.mkdir("data") unless Dir.exists?("data")
58
- Dir.mkdir("util") unless Dir.exists?("util")
59
- Dir.mkdir("storage") unless Dir.exists?("storage")
57
+ Dir.mkdir("data") unless Dir.exist?("data")
58
+ Dir.mkdir("util") unless Dir.exist?("util")
59
+ Dir.mkdir("storage") unless Dir.exist?("storage")
60
60
  Dir.chdir("storage") do
61
- Dir.mkdir("yale") unless Dir.exists?("yale")
62
- Dir.mkdir("list") unless Dir.exists?("list")
63
- Dir.mkdir("dense") unless Dir.exists?("dense")
61
+ Dir.mkdir("yale") unless Dir.exist?("yale")
62
+ Dir.mkdir("list") unless Dir.exist?("list")
63
+ Dir.mkdir("dense") unless Dir.exist?("dense")
64
64
  end
65
65
 
66
66
  # to clean up object files in subdirectories:
@@ -339,12 +339,12 @@ namespace smmp_sort {
339
339
  if (a < b) {
340
340
  if (b < c) return b; // a b c
341
341
  if (a < c) return c; // a c b
342
- return a; // c a b
342
+ return a; // c a b
343
343
 
344
344
  } else { // a > b
345
345
  if (a < c) return a; // b a c
346
346
  if (b < c) return c; // b c a
347
- return b; // c b a
347
+ return b; // c b a
348
348
  }
349
349
  }
350
350
 
@@ -338,6 +338,7 @@ NM_DEF_STRUCT_POST(NMATRIX); // };
338
338
  /* Structs for dealing with VALUEs in use so that they don't get GC'd */
339
339
 
340
340
  NM_DEF_STRUCT_PRE(NM_GC_LL_NODE); // struct NM_GC_LL_NODE {
341
+ VALUE* key; // VALUE* key;
341
342
  VALUE* val; // VALUE* val;
342
343
  size_t n; // size_t n;
343
344
  NM_DECL_STRUCT(NM_GC_LL_NODE*, next); // NM_GC_LL_NODE* next;
@@ -375,6 +376,14 @@ NM_DEF_STRUCT_POST(NM_GC_HOLDER); // };
375
376
  // enum for a list of possible storage types.
376
377
  #define NM_STYPE(val) (NM_STRUCT(val)->stype)
377
378
 
379
+ /*
380
+ * Object dtype storage keeps Ruby VALUEs in malloc-backed arrays/lists, not in
381
+ * Ruby object fields. After mutating that native storage, make the wrapper
382
+ * object write-barrier-unprotected so minor GC runs nm_mark and sees newly
383
+ * assigned VALUEs before they can be collected.
384
+ */
385
+ #define NM_OBJECT_STORAGE_WB_UNPROTECT(val) RB_OBJ_WB_UNPROTECT(val)
386
+
378
387
  // Get the shape of the ith dimension (int)
379
388
  #define NM_SHAPE(val,i) (NM_STORAGE(val)->shape[(i)])
380
389
 
@@ -401,7 +410,7 @@ NM_DEF_STRUCT_POST(NM_GC_HOLDER); // };
401
410
 
402
411
  #define NM_CHECK_ALLOC(x) if (!x) rb_raise(rb_eNoMemError, "insufficient memory");
403
412
 
404
- #define RB_FILE_EXISTS(fn) (rb_funcall(rb_const_get(rb_cObject, rb_intern("File")), rb_intern("exists?"), 1, (fn)) == Qtrue)
413
+ #define RB_FILE_EXISTS(fn) (rb_funcall(rb_const_get(rb_cObject, rb_intern("File")), rb_intern("exist?"), 1, (fn)) == Qtrue)
405
414
 
406
415
  #define IsNMatrixType(v) (RB_TYPE_P(v, T_DATA) && (RDATA(v)->dfree == (RUBY_DATA_FUNC)nm_delete || RDATA(v)->dfree == (RUBY_DATA_FUNC)nm_delete_ref))
407
416
  #define CheckNMatrixType(v) if (!IsNMatrixType(v)) rb_raise(rb_eTypeError, "expected NMatrix on left-hand side of operation");
@@ -409,10 +409,17 @@ void Init_nmatrix() {
409
409
  */
410
410
  static VALUE nm_alloc(VALUE klass) {
411
411
  NMATRIX* mat = NM_ALLOC(NMATRIX);
412
+ mat->stype = nm::DENSE_STORE;
412
413
  mat->storage = NULL;
413
414
 
414
- // DO NOT MARK This STRUCT. It has no storage allocated, and no stype, so mark will do an invalid something.
415
- return Data_Wrap_Struct(klass, NULL, nm_delete, mat);
415
+ /*
416
+ * Newly allocated NMatrix wrappers do not have storage yet, but nm_mark is
417
+ * explicitly null-storage safe. Installing it here is required for object
418
+ * dtype matrices after initialization: otherwise Ruby never asks the matrix
419
+ * to mark VALUEs stored in native dense/list/Yale memory, so a later GC can
420
+ * reclaim objects that were assigned into the matrix.
421
+ */
422
+ return Data_Wrap_Struct(klass, nm_mark, nm_delete, mat);
416
423
  }
417
424
 
418
425
  /*
@@ -454,6 +461,8 @@ static VALUE nm_capacity(VALUE self) {
454
461
  * Mark function.
455
462
  */
456
463
  void nm_mark(NMATRIX* mat) {
464
+ if (!mat || !mat->storage) return;
465
+
457
466
  STYPE_MARK_TABLE(mark)
458
467
  mark[mat->stype](mat->storage);
459
468
  }
@@ -463,12 +472,17 @@ void nm_mark(NMATRIX* mat) {
463
472
  * Destructor.
464
473
  */
465
474
  void nm_delete(NMATRIX* mat) {
475
+ if (!mat) return;
476
+
466
477
  static void (*ttable[nm::NUM_STYPES])(STORAGE*) = {
467
478
  nm_dense_storage_delete,
468
479
  nm_list_storage_delete,
469
480
  nm_yale_storage_delete
470
481
  };
471
- ttable[mat->stype](mat->storage);
482
+
483
+ if (mat->storage && mat->stype < nm::NUM_STYPES) {
484
+ ttable[mat->stype](mat->storage);
485
+ }
472
486
 
473
487
  NM_FREE(mat);
474
488
  }
@@ -477,12 +491,17 @@ void nm_delete(NMATRIX* mat) {
477
491
  * Slicing destructor.
478
492
  */
479
493
  void nm_delete_ref(NMATRIX* mat) {
494
+ if (!mat) return;
495
+
480
496
  static void (*ttable[nm::NUM_STYPES])(STORAGE*) = {
481
497
  nm_dense_storage_delete_ref,
482
498
  nm_list_storage_delete_ref,
483
499
  nm_yale_storage_delete_ref
484
500
  };
485
- ttable[mat->stype](mat->storage);
501
+
502
+ if (mat->storage && mat->stype < nm::NUM_STYPES) {
503
+ ttable[mat->stype](mat->storage);
504
+ }
486
505
 
487
506
  NM_FREE(mat);
488
507
  }
@@ -503,7 +522,9 @@ static void __nm_mark_value_container(NM_GC_HOLDER* gc_value_holder_struct) {
503
522
  if (gc_value_holder_struct && gc_value_holder_struct->start) {
504
523
  NM_GC_LL_NODE* curr = gc_value_holder_struct->start;
505
524
  while (curr) {
506
- rb_gc_mark_locations(curr->val, curr->val + curr->n);
525
+ if (curr->val && curr->n) {
526
+ rb_gc_mark_locations(curr->val, curr->val + curr->n);
527
+ }
507
528
  curr = curr->next;
508
529
  }
509
530
  }
@@ -540,7 +561,13 @@ void nm_register_values(VALUE* values, size_t n) {
540
561
  } else {
541
562
  to_insert = NM_ALLOC_NONRUBY(NM_GC_LL_NODE);
542
563
  }
543
- to_insert->val = values;
564
+ to_insert->key = values;
565
+ if (n) {
566
+ to_insert->val = NM_ALLOC_N(VALUE, n);
567
+ memcpy(to_insert->val, values, sizeof(VALUE) * n);
568
+ } else {
569
+ to_insert->val = NULL;
570
+ }
544
571
  to_insert->n = n;
545
572
  to_insert->next = gc_value_holder_struct->start;
546
573
  gc_value_holder_struct->start = to_insert;
@@ -557,13 +584,17 @@ void nm_unregister_values(VALUE* values, size_t n) {
557
584
  NM_GC_LL_NODE* curr = gc_value_holder_struct->start;
558
585
  NM_GC_LL_NODE* last = NULL;
559
586
  while (curr) {
560
- if (curr->val == values) {
587
+ if (curr->key == values) {
561
588
  if (last) {
562
589
  last->next = curr->next;
563
590
  } else {
564
591
  gc_value_holder_struct->start = curr->next;
565
592
  }
593
+ if (curr->val) {
594
+ NM_FREE(curr->val);
595
+ }
566
596
  curr->next = allocated_pool->start;
597
+ curr->key = NULL;
567
598
  curr->val = NULL;
568
599
  curr->n = 0;
569
600
  allocated_pool->start = curr;
@@ -602,14 +633,18 @@ void nm_completely_unregister_value(VALUE* val) {
602
633
  NM_GC_LL_NODE* curr = gc_value_holder_struct->start;
603
634
  NM_GC_LL_NODE* last = NULL;
604
635
  while (curr) {
605
- if (curr->val == val) {
636
+ if (curr->key == val) {
606
637
  if (last) {
607
638
  last->next = curr->next;
608
639
  } else {
609
640
  gc_value_holder_struct->start = curr->next;
610
641
  }
611
642
  NM_GC_LL_NODE* temp_next = curr->next;
643
+ if (curr->val) {
644
+ NM_FREE(curr->val);
645
+ }
612
646
  curr->next = allocated_pool->start;
647
+ curr->key = NULL;
613
648
  curr->val = NULL;
614
649
  curr->n = 0;
615
650
  allocated_pool->start = curr;
@@ -1144,7 +1179,7 @@ static VALUE nm_init_new_version(int argc, VALUE* argv, VALUE self) {
1144
1179
  // Get the shape.
1145
1180
  size_t dim;
1146
1181
  size_t* shape = interpret_shape(shape_ary, &dim);
1147
- void* init;
1182
+ void* init = NULL;
1148
1183
  void* v = NULL;
1149
1184
  size_t v_size = 0;
1150
1185
 
@@ -102,17 +102,21 @@ namespace nm { namespace dense_storage {
102
102
  * Recursive function, sets multiple values in a matrix from a single source value. Same basic pattern as slice_copy.
103
103
  */
104
104
  template <typename D>
105
- static void slice_set(DENSE_STORAGE* dest, size_t* lengths, size_t pdest, size_t rank, D* const v, size_t v_size, size_t& v_offset) {
105
+ static void slice_set(VALUE owner, DENSE_STORAGE* dest, size_t* lengths, size_t pdest, size_t rank, D* const v, size_t v_size, size_t& v_offset) {
106
106
  if (dest->dim - rank > 1) {
107
107
  for (size_t i = 0; i < lengths[rank]; ++i) {
108
- slice_set<D>(dest, lengths, pdest + dest->stride[rank] * i, rank + 1, v, v_size, v_offset);
108
+ slice_set<D>(owner, dest, lengths, pdest + dest->stride[rank] * i, rank + 1, v, v_size, v_offset);
109
109
  }
110
110
  } else {
111
111
  for (size_t p = 0; p < lengths[rank]; ++p, ++v_offset) {
112
112
  if (v_offset >= v_size) v_offset %= v_size;
113
113
 
114
114
  D* elem = reinterpret_cast<D*>(dest->elements);
115
- elem[p + pdest] = v[v_offset];
115
+ if (dest->dtype == nm::RUBYOBJ) {
116
+ RB_OBJ_WRITE(owner, reinterpret_cast<VALUE*>(elem) + p + pdest, reinterpret_cast<VALUE*>(v)[v_offset]);
117
+ } else {
118
+ elem[p + pdest] = v[v_offset];
119
+ }
116
120
  }
117
121
  }
118
122
  }
@@ -127,6 +131,9 @@ namespace nm { namespace dense_storage {
127
131
  NM_CONSERVATIVE(nm_register_value(&right));
128
132
 
129
133
  DENSE_STORAGE* s = NM_STORAGE_DENSE(left);
134
+ if (s->dtype == nm::RUBYOBJ) {
135
+ NM_OBJECT_STORAGE_WB_UNPROTECT(left);
136
+ }
130
137
 
131
138
  std::pair<NMATRIX*,bool> nm_and_free =
132
139
  interpret_arg_as_dense_nmatrix(right, s->dtype);
@@ -158,10 +165,15 @@ namespace nm { namespace dense_storage {
158
165
  }
159
166
 
160
167
  if (slice->single) {
161
- reinterpret_cast<D*>(s->elements)[nm_dense_storage_pos(s, slice->coords)] = *v;
168
+ size_t pos = nm_dense_storage_pos(s, slice->coords);
169
+ if (s->dtype == nm::RUBYOBJ) {
170
+ RB_OBJ_WRITE(left, reinterpret_cast<VALUE*>(s->elements) + pos, *reinterpret_cast<VALUE*>(v));
171
+ } else {
172
+ reinterpret_cast<D*>(s->elements)[pos] = *v;
173
+ }
162
174
  } else {
163
175
  size_t v_offset = 0;
164
- slice_set(s, slice->lengths, nm_dense_storage_pos(s, slice->coords), 0, v, v_size, v_offset);
176
+ slice_set(left, s, slice->lengths, nm_dense_storage_pos(s, slice->coords), 0, v, v_size, v_offset);
165
177
  }
166
178
 
167
179
  // Only free v if it was allocated in this function.
@@ -311,14 +323,25 @@ void nm_dense_storage_mark(STORAGE* storage_base) {
311
323
  DENSE_STORAGE* storage = (DENSE_STORAGE*)storage_base;
312
324
 
313
325
  if (storage && storage->dtype == nm::RUBYOBJ) {
314
- VALUE* els = reinterpret_cast<VALUE*>(storage->elements);
315
-
316
- if (els) {
317
- rb_gc_mark_locations(els, &(els[nm_storage_count_max_elements(storage)-1]));
326
+ /*
327
+ * Dense slice references share the source storage's elements pointer and
328
+ * may start at a non-zero offset. Marking only the reference's element
329
+ * count from offset zero can miss Ruby objects that are visible through
330
+ * the reference. Mark the backing source storage so every object that a
331
+ * live reference can expose stays alive.
332
+ */
333
+ DENSE_STORAGE* src = reinterpret_cast<DENSE_STORAGE*>(storage->src);
334
+
335
+ size_t count = src ? nm_storage_count_max_elements(src) : 0;
336
+ if (src && src->elements && count > 0) {
337
+ VALUE* els = reinterpret_cast<VALUE*>(src->elements);
338
+ /*
339
+ * This storage holds exact Ruby VALUEs, not arbitrary machine words.
340
+ * Mark each slot explicitly so objects assigned into malloc-backed
341
+ * matrix memory are kept alive just like Ruby object fields would be.
342
+ */
343
+ for (size_t i = 0; i < count; ++i) rb_gc_mark(els[i]);
318
344
  }
319
- //for (size_t index = nm_storage_count_max_elements(storage); index-- > 0;) {
320
- // rb_gc_mark(els[index]);
321
- //}
322
345
  }
323
346
  }
324
347
 
@@ -538,6 +538,9 @@ void set(VALUE left, SLICE* slice, VALUE right) {
538
538
  NM_CONSERVATIVE(nm_register_value(&left));
539
539
  NM_CONSERVATIVE(nm_register_value(&right));
540
540
  LIST_STORAGE* s = NM_STORAGE_LIST(left);
541
+ if (s->dtype == nm::RUBYOBJ) {
542
+ NM_OBJECT_STORAGE_WB_UNPROTECT(left);
543
+ }
541
544
 
542
545
  std::pair<NMATRIX*,bool> nm_and_free =
543
546
  interpret_arg_as_dense_nmatrix(right, NM_DTYPE(left));
@@ -779,7 +782,7 @@ void nm_list_storage_unregister(const STORAGE* s) {
779
782
  */
780
783
  static NODE* list_storage_get_single_node(LIST_STORAGE* s, SLICE* slice) {
781
784
  LIST* l = s->rows;
782
- NODE* n;
785
+ NODE* n = NULL;
783
786
 
784
787
  for (size_t r = 0; r < s->dim; r++) {
785
788
  n = nm::list::find(l, s->offset[r] + slice->coords[r]);
@@ -1160,11 +1163,12 @@ void* nm_list_storage_get(const STORAGE* storage, SLICE* slice) {
1160
1163
  void* nm_list_storage_ref(const STORAGE* storage, SLICE* slice) {
1161
1164
  LIST_STORAGE* s = (LIST_STORAGE*)storage;
1162
1165
  LIST_STORAGE* ns = NULL;
1166
+ NODE* n = NULL;
1163
1167
  nm_list_storage_register(s);
1164
1168
 
1165
1169
  //TODO: It needs a refactoring.
1166
1170
  if (slice->single) {
1167
- NODE* n = list_storage_get_single_node(s, slice);
1171
+ n = list_storage_get_single_node(s, slice);
1168
1172
  nm_list_storage_unregister(s);
1169
1173
  return (n ? n->val : s->default_val);
1170
1174
  } else {
@@ -50,7 +50,18 @@ public:
50
50
  : s(reinterpret_cast<YALE_STORAGE*>(storage->src)),
51
51
  slice(storage != storage->src),
52
52
  slice_shape(storage->shape),
53
- slice_offset(storage->offset)
53
+ slice_offset(storage->offset),
54
+ owner(Qnil)
55
+ {
56
+ nm_yale_storage_register(storage->src);
57
+ }
58
+
59
+ YaleStorage(const YALE_STORAGE* storage, VALUE owner_)
60
+ : s(reinterpret_cast<YALE_STORAGE*>(storage->src)),
61
+ slice(storage != storage->src),
62
+ slice_shape(storage->shape),
63
+ slice_offset(storage->offset),
64
+ owner(owner_)
54
65
  {
55
66
  nm_yale_storage_register(storage->src);
56
67
  }
@@ -59,7 +70,8 @@ public:
59
70
  : s(reinterpret_cast<YALE_STORAGE*>(storage->src)),
60
71
  slice(storage != storage->src),
61
72
  slice_shape(storage->shape),
62
- slice_offset(storage->offset)
73
+ slice_offset(storage->offset),
74
+ owner(Qnil)
63
75
  {
64
76
  nm_yale_storage_register(reinterpret_cast<STORAGE*>(storage->src));
65
77
  }
@@ -96,6 +108,17 @@ public:
96
108
  inline const D& a(size_t p) const { return a_p()[p]; }
97
109
  inline D& a(size_t p) { return a_p()[p]; }
98
110
 
111
+ // Object matrices store Ruby VALUEs in native memory, so replacing a value must
112
+ // go through Ruby's write barrier. Without it, generational GC can collect a
113
+ // newly assigned object before the matrix is marked again.
114
+ inline void write_a(size_t p, const D& val) {
115
+ if (dtype() == nm::RUBYOBJ && owner != Qnil) {
116
+ RB_OBJ_WRITE(owner, reinterpret_cast<VALUE*>(s->a) + p, reinterpret_cast<const VALUE*>(&val)[0]);
117
+ } else {
118
+ a(p) = val;
119
+ }
120
+ }
121
+
99
122
  bool real_row_empty(size_t i) const { return ija(i+1) - ija(i) == 0 ? true : false; }
100
123
 
101
124
  inline size_t* shape_p() const { return slice_shape; }
@@ -467,7 +490,7 @@ public:
467
490
  * A pseudo-insert operation, since the diagonal portion of the A array is constant size.
468
491
  */
469
492
  stored_diagonal_iterator insert(stored_diagonal_iterator position, const D& val) {
470
- *position = val;
493
+ write_a(position.p(), val);
471
494
  return position;
472
495
  }
473
496
 
@@ -694,24 +717,37 @@ public:
694
717
 
695
718
  E* ns_a = reinterpret_cast<E*>(ns.a);
696
719
  size_t sz = shape(0) + 1; // current used size of ns
697
- nm_yale_storage_register(&ns);
720
+ E converted = val;
721
+ if (ns.dtype == nm::RUBYOBJ) {
722
+ nm_register_value(reinterpret_cast<VALUE*>(&converted));
723
+ }
698
724
 
699
725
  // FIXME: If diagonals line up, it's probably faster to do this with stored diagonal and stored non-diagonal iterators
700
726
  for (const_row_iterator it = cribegin(); it != criend(); ++it) {
701
727
  for (auto jt = it.begin(); !jt.end(); ++jt) {
702
728
  if (it.i() == jt.j()) {
703
- if (Yield) ns_a[it.i()] = rb_yield(~jt);
704
- else ns_a[it.i()] = static_cast<E>(*jt);
729
+ if (Yield) converted = E(rb_yield(~jt));
730
+ else converted = static_cast<E>(*jt);
731
+ if (ns.dtype == nm::RUBYOBJ) {
732
+ nm_register_value(reinterpret_cast<VALUE*>(&converted));
733
+ }
734
+ ns_a[it.i()] = converted;
705
735
  } else if (*jt != const_default_obj()) {
706
- if (Yield) ns_a[sz] = rb_yield(~jt);
707
- else ns_a[sz] = static_cast<E>(*jt);
736
+ if (Yield) converted = E(rb_yield(~jt));
737
+ else converted = static_cast<E>(*jt);
738
+ if (ns.dtype == nm::RUBYOBJ) {
739
+ nm_register_value(reinterpret_cast<VALUE*>(&converted));
740
+ }
741
+ ns_a[sz] = converted;
708
742
  ns.ija[sz] = jt.j();
709
743
  ++sz;
710
744
  }
711
745
  }
712
746
  ns.ija[it.i()+1] = sz;
713
747
  }
714
- nm_yale_storage_unregister(&ns);
748
+ if (ns.dtype == nm::RUBYOBJ) {
749
+ nm_unregister_value(reinterpret_cast<VALUE*>(&converted));
750
+ }
715
751
 
716
752
  //ns.ija[shape(0)] = sz; // indicate end of last row
717
753
  ns.ndnz = sz - shape(0) - 1; // update ndnz count
@@ -751,14 +787,24 @@ public:
751
787
 
752
788
  E* la = reinterpret_cast<E*>(lhs->a);
753
789
 
754
- nm_yale_storage_register(lhs);
790
+ E converted;
791
+ if (lhs->dtype == nm::RUBYOBJ) {
792
+ nm_register_value(reinterpret_cast<VALUE*>(&converted));
793
+ }
755
794
  for (size_t m = 0; m < size(); ++m) {
756
795
  if (Yield) {
757
- la[m] = rb_yield(nm::yale_storage::nm_rb_dereference(a(m)));
758
- }
759
- else la[m] = static_cast<E>(a(m));
796
+ converted = E(rb_yield(nm::yale_storage::nm_rb_dereference(a(m))));
797
+ } else {
798
+ converted = static_cast<E>(a(m));
799
+ }
800
+ if (lhs->dtype == nm::RUBYOBJ) {
801
+ nm_register_value(reinterpret_cast<VALUE*>(&converted));
802
+ }
803
+ la[m] = converted;
804
+ }
805
+ if (lhs->dtype == nm::RUBYOBJ) {
806
+ nm_unregister_value(reinterpret_cast<VALUE*>(&converted));
760
807
  }
761
- nm_yale_storage_unregister(lhs);
762
808
 
763
809
  }
764
810
 
@@ -995,10 +1041,18 @@ protected:
995
1041
  if (v_offset >= v_size) v_offset %= v_size;
996
1042
 
997
1043
  if (j + real_j == i + real_i) { // modify diagonal
998
- new_a[real_i + i] = v[v_offset];
1044
+ if (s->dtype == nm::RUBYOBJ && owner != Qnil) {
1045
+ RB_OBJ_WRITE(owner, reinterpret_cast<VALUE*>(new_a) + real_i + i, reinterpret_cast<const VALUE*>(v)[v_offset]);
1046
+ } else {
1047
+ new_a[real_i + i] = v[v_offset];
1048
+ }
999
1049
  } else if (v[v_offset] != const_default_obj()) {
1000
1050
  new_ija[q] = j + real_j;
1001
- new_a[q] = v[v_offset];
1051
+ if (s->dtype == nm::RUBYOBJ && owner != Qnil) {
1052
+ RB_OBJ_WRITE(owner, reinterpret_cast<VALUE*>(new_a) + q, reinterpret_cast<const VALUE*>(v)[v_offset]);
1053
+ } else {
1054
+ new_a[q] = v[v_offset];
1055
+ }
1002
1056
  ++q; // move on to next q location
1003
1057
  }
1004
1058
 
@@ -1132,6 +1186,7 @@ protected:
1132
1186
  bool slice;
1133
1187
  size_t* slice_shape;
1134
1188
  size_t* slice_offset;
1189
+ VALUE owner;
1135
1190
  };
1136
1191
 
1137
1192
  } // end of nm namespace
@@ -303,7 +303,7 @@ public:
303
303
  while (!position.end() && position.j() < jj) ++position; // position is just a hint. (This loop ideally only has to happen once.)
304
304
 
305
305
  if (!position.end() && position.j() == jj) {
306
- *position = val; // replace existing
306
+ y.write_a(position.p(), val); // replace existing
307
307
  } else {
308
308
 
309
309
  if (sz + 1 > y.capacity()) {
@@ -313,7 +313,7 @@ public:
313
313
  y.update_real_row_sizes_from(real_i(), 1);
314
314
  }
315
315
  ija(position.p()) = jj + y.offset(1); // set column ID
316
- a(position.p()) = val;
316
+ y.write_a(position.p(), val);
317
317
  adjust_length(1);
318
318
  }
319
319
 
@@ -331,7 +331,7 @@ public:
331
331
  */
332
332
  //template <typename = typename std::enable_if<!std::is_const<RefType>::value>::type>
333
333
  void insert(size_t j, const D& val) {
334
- if (j + y.offset(1) == real_i()) a(real_i()) = val;
334
+ if (j + y.offset(1) == real_i()) y.write_a(real_i(), val);
335
335
  else {
336
336
  row_stored_nd_iterator jt = ndfind(j);
337
337
  if (!jt.end() && jt.j() == j) {
@@ -404,10 +404,10 @@ public:
404
404
  if (v_offset >= v_size) v_offset %= v_size; // reset v position.
405
405
 
406
406
  if (jc + y.offset(1) == real_i()) {
407
- y.a(real_i()) = v[v_offset]; // modify diagonal
407
+ y.write_a(real_i(), v[v_offset]); // modify diagonal
408
408
  } else if (v[v_offset] != y.const_default_obj()) {
409
409
  y.ija(pp) = jc; // modify non-diagonal
410
- y.a(pp) = v[v_offset];
410
+ y.write_a(pp, v[v_offset]);
411
411
  ++pp;
412
412
  }
413
413
  }
@@ -369,7 +369,10 @@ YALE_STORAGE* ref(YALE_STORAGE* s, SLICE* slice) {
369
369
  template <typename DType>
370
370
  void set(VALUE left, SLICE* slice, VALUE right) {
371
371
  YALE_STORAGE* storage = NM_STORAGE_YALE(left);
372
- YaleStorage<DType> y(storage);
372
+ if (storage->dtype == nm::RUBYOBJ) {
373
+ NM_OBJECT_STORAGE_WB_UNPROTECT(left);
374
+ }
375
+ YaleStorage<DType> y(storage, left);
373
376
  y.insert(slice, right);
374
377
  }
375
378
 
@@ -1456,9 +1459,25 @@ void nm_yale_storage_mark(STORAGE* storage_base) {
1456
1459
  YALE_STORAGE* storage = (YALE_STORAGE*)storage_base;
1457
1460
 
1458
1461
  if (storage && storage->dtype == nm::RUBYOBJ) {
1459
-
1460
- VALUE* a = (VALUE*)(storage->a);
1461
- rb_gc_mark_locations(a, &(a[storage->capacity-1]));
1462
+ /*
1463
+ * Yale slice references are lightweight wrappers around another
1464
+ * YALE_STORAGE. They may have no A array or capacity of their own, but
1465
+ * Ruby objects returned through the reference still live in the source
1466
+ * storage. Mark the source's initialized A entries so GC cannot reclaim
1467
+ * those objects while the reference is alive.
1468
+ */
1469
+ YALE_STORAGE* src = reinterpret_cast<YALE_STORAGE*>(storage->src);
1470
+
1471
+ size_t size = src ? nm::yale_storage::get_size(src) : 0;
1472
+ if (src && src->a && size > 0) {
1473
+ VALUE* a = reinterpret_cast<VALUE*>(src->a);
1474
+ /*
1475
+ * Yale object storage keeps real Ruby VALUEs in the native A array.
1476
+ * Marking them exactly is important after mutation, because otherwise
1477
+ * GC may reclaim recently assigned objects before later iteration.
1478
+ */
1479
+ for (size_t i = 0; i < size; ++i) rb_gc_mark(a[i]);
1480
+ }
1462
1481
  }
1463
1482
  }
1464
1483
 
@@ -69,7 +69,7 @@ class NMatrix
69
69
  NMatrix.new(4, [1.0, 0.0, 0.0, 0.0,
70
70
  0.0, c, -s, 0.0,
71
71
  0.0, s, c, 0.0,
72
- 0.0, 0.0, 0.0, 1.0], {dtype: :float64}.merge(opts))
72
+ 0.0, 0.0, 0.0, 1.0], **{dtype: :float64}.merge(opts))
73
73
  end
74
74
 
75
75
  def y_rotation angle_in_radians, opts={}
@@ -78,7 +78,7 @@ class NMatrix
78
78
  NMatrix.new(4, [ c, 0.0, s, 0.0,
79
79
  0.0, 1.0, 0.0, 0.0,
80
80
  -s, 0.0, c, 0.0,
81
- 0.0, 0.0, 0.0, 1.0], {dtype: :float64}.merge(opts))
81
+ 0.0, 0.0, 0.0, 1.0], **{dtype: :float64}.merge(opts))
82
82
  end
83
83
 
84
84
  def z_rotation angle_in_radians, opts={}
@@ -87,7 +87,7 @@ class NMatrix
87
87
  NMatrix.new(4, [ c, -s, 0.0, 0.0,
88
88
  s, c, 0.0, 0.0,
89
89
  0.0, 0.0, 1.0, 0.0,
90
- 0.0, 0.0, 0.0, 1.0], {dtype: :float64}.merge(opts))
90
+ 0.0, 0.0, 0.0, 1.0], **{dtype: :float64}.merge(opts))
91
91
  end
92
92
 
93
93
 
data/lib/nmatrix/mkmf.rb CHANGED
@@ -32,7 +32,7 @@ def find_newer_gplusplus #:nodoc:
32
32
  [9,8,7,6,5,4,3].each do |minor|
33
33
  ver = "4.#{minor}"
34
34
  gpp = "g++-#{ver}"
35
- result = `which #{gpp}`
35
+ result = `type #{gpp}`
36
36
  next if result.empty?
37
37
  CONFIG['CXX'] = gpp
38
38
  puts ver
@@ -69,14 +69,17 @@ if CONFIG['CXX'] == 'clang++'
69
69
  $CXX_STANDARD = 'c++11'
70
70
  else
71
71
  version = gplusplus_version
72
- if version < '4.3.0' && CONFIG['CXX'] == 'g++' # see if we can find a newer G++, unless it's been overridden by user
72
+ major_version = version.split('.')[0].to_i # string comparison fails past 10.0.0
73
+
74
+ if major_version < 5 and
75
+ version < '4.3.0' && CONFIG['CXX'] == 'g++' # see if we can find a newer G++, unless it's been overridden by user
73
76
  if !find_newer_gplusplus
74
77
  raise("You need a version of g++ which supports -std=c++0x or -std=c++11. If you're on a Mac and using Homebrew, we recommend using mac-brew-gcc.sh to install a more recent g++.")
75
78
  end
76
79
  version = gplusplus_version
77
80
  end
78
81
 
79
- if version < '4.7.0'
82
+ if major_version < 5 and version < '4.7.0'
80
83
  $CXX_STANDARD = 'c++0x'
81
84
  else
82
85
  $CXX_STANDARD = 'c++11'
@@ -727,7 +727,7 @@ class NMatrix
727
727
  end
728
728
 
729
729
  # Do the actual construction.
730
- n = NMatrix.new(new_shape, opts)
730
+ n = NMatrix.new(new_shape, **opts)
731
731
 
732
732
  # Figure out where to start concatenation. We don't know where it will end,
733
733
  # because each matrix may have own size along concat dimension.
@@ -1034,7 +1034,7 @@ class NMatrix
1034
1034
  def clone_structure(capacity = nil)
1035
1035
  opts = {stype: self.stype, default: self.default_value, dtype: self.dtype}
1036
1036
  opts = {capacity: capacity}.merge(opts) if self.yale?
1037
- NMatrix.new(self.shape, opts)
1037
+ NMatrix.new(self.shape, **opts)
1038
1038
  end
1039
1039
 
1040
1040
  #
@@ -1099,7 +1099,7 @@ protected
1099
1099
  # We can generally predict the change in capacity for Yale.
1100
1100
  opts = {capacity: self.capacity - self.shape[0] + new_shape[0]}.merge(opts)
1101
1101
  end
1102
- NMatrix.new(new_shape, opts)
1102
+ NMatrix.new(new_shape, **opts)
1103
1103
  end
1104
1104
 
1105
1105