koffi 1.1.0-beta.8 → 1.1.2

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.
Files changed (32) hide show
  1. package/CMakeLists.txt +3 -0
  2. package/README.md +13 -2
  3. package/build/qemu/1.1.2/koffi_darwin_x64.tar.gz +0 -0
  4. package/build/qemu/1.1.2/koffi_freebsd_arm64.tar.gz +0 -0
  5. package/build/qemu/1.1.2/koffi_freebsd_ia32.tar.gz +0 -0
  6. package/build/qemu/1.1.2/koffi_freebsd_x64.tar.gz +0 -0
  7. package/build/qemu/1.1.2/koffi_linux_arm.tar.gz +0 -0
  8. package/build/qemu/1.1.2/koffi_linux_arm64.tar.gz +0 -0
  9. package/build/qemu/1.1.2/koffi_linux_ia32.tar.gz +0 -0
  10. package/build/qemu/1.1.2/koffi_linux_x64.tar.gz +0 -0
  11. package/build/qemu/1.1.2/koffi_win32_ia32.tar.gz +0 -0
  12. package/build/qemu/1.1.2/koffi_win32_x64.tar.gz +0 -0
  13. package/package.json +2 -2
  14. package/qemu/qemu.js +1 -1
  15. package/src/call.cc +213 -44
  16. package/src/call.hh +2 -2
  17. package/src/ffi.cc +23 -18
  18. package/src/ffi.hh +3 -2
  19. package/src/parser.cc +30 -20
  20. package/src/parser.hh +3 -1
  21. package/src/util.hh +1 -1
  22. package/test/misc.c +17 -0
  23. package/build/qemu/1.1.0-beta.8/koffi_darwin_x64.tar.gz +0 -0
  24. package/build/qemu/1.1.0-beta.8/koffi_freebsd_arm64.tar.gz +0 -0
  25. package/build/qemu/1.1.0-beta.8/koffi_freebsd_ia32.tar.gz +0 -0
  26. package/build/qemu/1.1.0-beta.8/koffi_freebsd_x64.tar.gz +0 -0
  27. package/build/qemu/1.1.0-beta.8/koffi_linux_arm.tar.gz +0 -0
  28. package/build/qemu/1.1.0-beta.8/koffi_linux_arm64.tar.gz +0 -0
  29. package/build/qemu/1.1.0-beta.8/koffi_linux_ia32.tar.gz +0 -0
  30. package/build/qemu/1.1.0-beta.8/koffi_linux_x64.tar.gz +0 -0
  31. package/build/qemu/1.1.0-beta.8/koffi_win32_ia32.tar.gz +0 -0
  32. package/build/qemu/1.1.0-beta.8/koffi_win32_x64.tar.gz +0 -0
package/CMakeLists.txt CHANGED
@@ -23,6 +23,9 @@ if(MSVC)
23
23
  else()
24
24
  add_compile_options(-Wall -Wextra -Wno-missing-field-initializers
25
25
  -Wno-unused-parameter -Wno-class-memaccess)
26
+ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
27
+ add_compile_options(-Wno-unknown-warning-option)
28
+ endif()
26
29
  endif()
27
30
 
28
31
  # ---- Koffi ----
package/README.md CHANGED
@@ -20,10 +20,10 @@
20
20
 
21
21
  Koffi is a fast and easy-to-use FFI module for Node.js, with support for primitive and aggregate data types (structs), both by reference (pointer) and by value.
22
22
 
23
- After the release of version 1.0, the following features are planned:
23
+ The following features are planned in the near future:
24
24
 
25
- * 1.1: Asynchronous calls and fixed-size array types
26
25
  * 1.2: C to JS callbacks
26
+ * 1.3: Type parser
27
27
 
28
28
  The following platforms __are officially supported and tested__ at the moment:
29
29
 
@@ -231,6 +231,8 @@ Koffi exposes three functions to explore type information:
231
231
  Fixed-size arrays are declared with `koffi.array(type, length)`. Just like in C, they cannot be passed
232
232
  as functions parameters (they degenerate to pointers), or returned by value. You can however embed them in struct types.
233
233
 
234
+ ### JS typed arrays
235
+
234
236
  Special rules apply for arrays of primitive integer and float types (uint32_t, double, etc...):
235
237
  - When converting from JS to C, Koffi can take a normal Array (e.g. `[1, 2]`) or a TypedArray of the correct type (e.g. `Uint8Array` for an array of `uint8_t` numbers)
236
238
  - When converting from C to JS (for return value or output parameters), Koffi will by default use a TypedArray. But you can change this behavior when you create the array type with the optional hint argument: `koffi.array('uint8_t', 64, 'array')`
@@ -258,6 +260,14 @@ console.log(ReturnFoo1({ i: 5, a16: [6, 8] })) // Prints { i: 5, a16: Int16Array
258
260
  console.log(ReturnFoo2({ i: 5, a16: [6, 8] })) // Prints { i: 5, a16: [6, 8] }
259
261
  ```
260
262
 
263
+ ### C strings
264
+
265
+ Koffi can also convert JS strings to fixed-sized arrays in the following cases:
266
+ - char (or int8_t) arrays are filled with the UTF-8 encoded string, truncated if needed. The buffer is always NUL-terminated.
267
+ - char16 (or int16_t) arrays are filled with the UTF-16 encoded string, truncated if needed. The buffer is always NUL-terminated.
268
+
269
+ The reverse case is also true, Koffi can convert a C fixed-size buffer to a JS string. Use the `string` array hint to do this (e.g. `koffi.array('char', 8, 'string')`).
270
+
261
271
  ## Variadic functions
262
272
 
263
273
  Variadic functions are declared with an ellipsis as the last argument.
@@ -267,6 +277,7 @@ In order to call a variadic function, you must provide two Javascript arguments
267
277
  ```js
268
278
  const printf = lib.func('printf', 'int', ['string', '...']);
269
279
 
280
+ // The variadic arguments are: 6 (int), 8.5 (double), 'THE END' (const char *)
270
281
  printf('Integer %d, double %g, string %s', 'int', 6, 'double', 8.5, 'string', 'THE END');
271
282
  ```
272
283
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "1.1.0-beta.8",
3
+ "version": "1.1.2",
4
4
  "description": "Fast and simple FFI (foreign function interface) for Node.js",
5
5
  "keywords": [
6
6
  "foreign",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "license": "AGPL-3.0",
26
26
  "dependencies": {
27
- "cnoke": "^1.0.7"
27
+ "cnoke": "^1.0.9"
28
28
  },
29
29
  "devDependencies": {
30
30
  "chalk": "^4.1.2",
package/qemu/qemu.js CHANGED
@@ -395,7 +395,7 @@ async function pack() {
395
395
  }));
396
396
 
397
397
  let status = copied ? chalk.bold.green('[ok]') : chalk.bold.red('[error]');
398
- log(machine, 'Download', status);
398
+ log(machine, 'Pack', status);
399
399
  }));
400
400
  }
401
401
 
package/src/call.cc CHANGED
@@ -142,21 +142,77 @@ bool CallData::PushObject(const Napi::Object &obj, const TypeInfo *type, uint8_t
142
142
  bool b = value.As<Napi::Boolean>();
143
143
  *(bool *)dest = b;
144
144
  } break;
145
- case PrimitiveKind::Int8:
146
- case PrimitiveKind::UInt8:
147
- case PrimitiveKind::Int16:
148
- case PrimitiveKind::UInt16:
149
- case PrimitiveKind::Int32:
150
- case PrimitiveKind::UInt32:
151
- case PrimitiveKind::Int64:
152
- case PrimitiveKind::UInt64: {
145
+ case PrimitiveKind::Int8: {
146
+ if (RG_UNLIKELY(!value.IsNumber() && !value.IsBigInt())) {
147
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for member '%2', expected number", GetValueType(instance, value), member.name);
148
+ return false;
149
+ }
150
+
151
+ int8_t v = CopyNumber<int8_t>(value);
152
+ *(int8_t *)dest = v;
153
+ } break;
154
+ case PrimitiveKind::UInt8: {
155
+ if (RG_UNLIKELY(!value.IsNumber() && !value.IsBigInt())) {
156
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for member '%2', expected number", GetValueType(instance, value), member.name);
157
+ return false;
158
+ }
159
+
160
+ uint8_t v = CopyNumber<uint8_t>(value);
161
+ *(uint8_t *)dest = v;
162
+ } break;
163
+ case PrimitiveKind::Int16: {
164
+ if (RG_UNLIKELY(!value.IsNumber() && !value.IsBigInt())) {
165
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for member '%2', expected number", GetValueType(instance, value), member.name);
166
+ return false;
167
+ }
168
+
169
+ int16_t v = CopyNumber<int16_t>(value);
170
+ *(int16_t *)dest = v;
171
+ } break;
172
+ case PrimitiveKind::UInt16: {
173
+ if (RG_UNLIKELY(!value.IsNumber() && !value.IsBigInt())) {
174
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for member '%2', expected number", GetValueType(instance, value), member.name);
175
+ return false;
176
+ }
177
+
178
+ uint16_t v = CopyNumber<uint16_t>(value);
179
+ *(uint16_t *)dest = v;
180
+ } break;
181
+ case PrimitiveKind::Int32: {
182
+ if (RG_UNLIKELY(!value.IsNumber() && !value.IsBigInt())) {
183
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for member '%2', expected number", GetValueType(instance, value), member.name);
184
+ return false;
185
+ }
186
+
187
+ int32_t v = CopyNumber<int32_t>(value);
188
+ *(int32_t *)dest = v;
189
+ } break;
190
+ case PrimitiveKind::UInt32: {
191
+ if (RG_UNLIKELY(!value.IsNumber() && !value.IsBigInt())) {
192
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for member '%2', expected number", GetValueType(instance, value), member.name);
193
+ return false;
194
+ }
195
+
196
+ uint32_t v = CopyNumber<uint32_t>(value);
197
+ *(uint32_t *)dest = v;
198
+ } break;
199
+ case PrimitiveKind::Int64: {
153
200
  if (RG_UNLIKELY(!value.IsNumber() && !value.IsBigInt())) {
154
201
  ThrowError<Napi::TypeError>(env, "Unexpected %1 value for member '%2', expected number", GetValueType(instance, value), member.name);
155
202
  return false;
156
203
  }
157
204
 
158
205
  int64_t v = CopyNumber<int64_t>(value);
159
- memcpy(dest, &v, member.type->size); // Little Endian
206
+ *(int64_t *)dest = v;
207
+ } break;
208
+ case PrimitiveKind::UInt64: {
209
+ if (RG_UNLIKELY(!value.IsNumber() && !value.IsBigInt())) {
210
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for member '%2', expected number", GetValueType(instance, value), member.name);
211
+ return false;
212
+ }
213
+
214
+ uint64_t v = CopyNumber<uint64_t>(value);
215
+ *(uint64_t *)dest = v;
160
216
  } break;
161
217
  case PrimitiveKind::String: {
162
218
  if (RG_UNLIKELY(!value.IsString())) {
@@ -196,12 +252,12 @@ bool CallData::PushObject(const Napi::Object &obj, const TypeInfo *type, uint8_t
196
252
  return false;
197
253
  }
198
254
 
199
- Napi::Object obj = value.As<Napi::Object>();
200
- if (!PushObject(obj, member.type, dest, realign))
255
+ Napi::Object obj2 = value.As<Napi::Object>();
256
+ if (!PushObject(obj2, member.type, dest, realign))
201
257
  return false;
202
258
  } break;
203
259
  case PrimitiveKind::Array: {
204
- if (RG_UNLIKELY(!value.IsArray() && !value.IsTypedArray())) {
260
+ if (RG_UNLIKELY(!value.IsArray() && !value.IsTypedArray() && !value.IsString())) {
205
261
  ThrowError<Napi::TypeError>(env, "Unexpected value %1 for member '%2', expected array", GetValueType(instance, value), member.name);
206
262
  return false;
207
263
  }
@@ -236,9 +292,9 @@ bool CallData::PushObject(const Napi::Object &obj, const TypeInfo *type, uint8_t
236
292
  return true;
237
293
  }
238
294
 
239
- bool CallData::PushArray(const Napi::Object &obj, const TypeInfo *type, uint8_t *dest, int16_t realign)
295
+ bool CallData::PushArray(const Napi::Value &obj, const TypeInfo *type, uint8_t *dest, int16_t realign)
240
296
  {
241
- RG_ASSERT(obj.IsArray() || obj.IsTypedArray());
297
+ RG_ASSERT(obj.IsArray() || obj.IsTypedArray() || obj.IsString());
242
298
  RG_ASSERT(type->primitive == PrimitiveKind::Array);
243
299
 
244
300
  uint32_t len = type->size / type->ref->size;
@@ -279,17 +335,52 @@ bool CallData::PushArray(const Napi::Object &obj, const TypeInfo *type, uint8_t
279
335
  *(bool *)dest = b;
280
336
  });
281
337
  } break;
282
- case PrimitiveKind::Int8:
283
- case PrimitiveKind::UInt8:
284
- case PrimitiveKind::Int16:
285
- case PrimitiveKind::UInt16:
286
- case PrimitiveKind::Int32:
287
- case PrimitiveKind::UInt32:
288
- case PrimitiveKind::Int64:
289
- case PrimitiveKind::UInt64: {
338
+ case PrimitiveKind::Int8: {
339
+ PUSH_ARRAY(value.IsNumber() || value.IsBigInt(), "number", {
340
+ int8_t v = CopyNumber<int8_t>(value);
341
+ *(int8_t *)dest = v;
342
+ });
343
+ } break;
344
+ case PrimitiveKind::UInt8: {
345
+ PUSH_ARRAY(value.IsNumber() || value.IsBigInt(), "number", {
346
+ uint8_t v = CopyNumber<uint8_t>(value);
347
+ *(uint8_t *)dest = v;
348
+ });
349
+ } break;
350
+ case PrimitiveKind::Int16: {
351
+ PUSH_ARRAY(value.IsNumber() || value.IsBigInt(), "number", {
352
+ int16_t v = CopyNumber<int16_t>(value);
353
+ *(int16_t *)dest = v;
354
+ });
355
+ } break;
356
+ case PrimitiveKind::UInt16: {
357
+ PUSH_ARRAY(value.IsNumber() || value.IsBigInt(), "number", {
358
+ uint16_t v = CopyNumber<uint16_t>(value);
359
+ *(uint16_t *)dest = v;
360
+ });
361
+ } break;
362
+ case PrimitiveKind::Int32: {
363
+ PUSH_ARRAY(value.IsNumber() || value.IsBigInt(), "number", {
364
+ int32_t v = CopyNumber<int32_t>(value);
365
+ *(int32_t *)dest = v;
366
+ });
367
+ } break;
368
+ case PrimitiveKind::UInt32: {
369
+ PUSH_ARRAY(value.IsNumber() || value.IsBigInt(), "number", {
370
+ uint32_t v = CopyNumber<uint32_t>(value);
371
+ *(uint32_t *)dest = v;
372
+ });
373
+ } break;
374
+ case PrimitiveKind::Int64: {
290
375
  PUSH_ARRAY(value.IsNumber() || value.IsBigInt(), "number", {
291
376
  int64_t v = CopyNumber<int64_t>(value);
292
- memcpy(dest, &v, type->ref->size); // Little Endian
377
+ *(int64_t *)dest = v;
378
+ });
379
+ } break;
380
+ case PrimitiveKind::UInt64: {
381
+ PUSH_ARRAY(value.IsNumber() || value.IsBigInt(), "number", {
382
+ uint64_t v = CopyNumber<uint64_t>(value);
383
+ *(uint64_t *)dest = v;
293
384
  });
294
385
  } break;
295
386
  case PrimitiveKind::String: {
@@ -316,15 +407,15 @@ bool CallData::PushArray(const Napi::Object &obj, const TypeInfo *type, uint8_t
316
407
  } break;
317
408
  case PrimitiveKind::Record: {
318
409
  PUSH_ARRAY(IsObject(value), "object", {
319
- Napi::Object obj = value.As<Napi::Object>();
320
- if (!PushObject(obj, type->ref, dest, realign))
410
+ Napi::Object obj2 = value.As<Napi::Object>();
411
+ if (!PushObject(obj2, type->ref, dest, realign))
321
412
  return false;
322
413
  });
323
414
  } break;
324
415
  case PrimitiveKind::Array: {
325
- PUSH_ARRAY(value.IsArray() || value.IsTypedArray(), "array", {
326
- Napi::Object array = value.As<Napi::Array>();
327
- if (!PushArray(array, type->ref, dest, realign))
416
+ PUSH_ARRAY(value.IsArray() || value.IsTypedArray() || value.IsString(), "array", {
417
+ Napi::Object array2 = value.As<Napi::Array>();
418
+ if (!PushArray(array2, type->ref, dest, realign))
328
419
  return false;
329
420
  });
330
421
  } break;
@@ -343,7 +434,7 @@ bool CallData::PushArray(const Napi::Object &obj, const TypeInfo *type, uint8_t
343
434
  }
344
435
 
345
436
  #undef PUSH_ARRAY
346
- } else {
437
+ } else if (obj.IsTypedArray()) {
347
438
  Napi::TypedArray array = obj.As<Napi::TypedArray>();
348
439
  const uint8_t *buf = (const uint8_t *)array.ArrayBuffer().Data();
349
440
 
@@ -378,6 +469,25 @@ bool CallData::PushArray(const Napi::Object &obj, const TypeInfo *type, uint8_t
378
469
 
379
470
  dest += type->ref->size;
380
471
  }
472
+ } else if (obj.IsString()) {
473
+ size_t encoded = 0;
474
+
475
+ if (type->ref->primitive == PrimitiveKind::Int8 || type->ref->primitive == PrimitiveKind::UInt8) {
476
+ napi_status status = napi_get_value_string_utf8(env, obj, (char *)dest, type->size, &encoded);
477
+ RG_ASSERT(status == napi_ok);
478
+ } else if (type->ref->primitive == PrimitiveKind::Int16 || type->ref->primitive == PrimitiveKind::UInt16) {
479
+ napi_status status = napi_get_value_string_utf16(env, obj, (char16_t *)dest, type->size / 2, &encoded);
480
+ RG_ASSERT(status == napi_ok);
481
+
482
+ encoded *= 2;
483
+ } else {
484
+ ThrowError<Napi::TypeError>(env, "Strings cannot be converted to %1 array", type->ref->name);
485
+ return false;
486
+ }
487
+
488
+ memset_safe(dest + encoded, 0, type->size - encoded);
489
+ } else {
490
+ RG_UNREACHABLE();
381
491
  }
382
492
 
383
493
  return true;
@@ -451,8 +561,8 @@ void CallData::PopObject(Napi::Object obj, const uint8_t *src, const TypeInfo *t
451
561
  obj.Set(member.name, obj2);
452
562
  } break;
453
563
  case PrimitiveKind::Array: {
454
- Napi::Object obj2 = PopArray(src, member.type, realign);
455
- obj.Set(member.name, obj2);
564
+ Napi::Value value = PopArray(src, member.type, realign);
565
+ obj.Set(member.name, value);
456
566
  } break;
457
567
  case PrimitiveKind::Float32: {
458
568
  float f;
@@ -477,7 +587,18 @@ Napi::Object CallData::PopObject(const uint8_t *src, const TypeInfo *type, int16
477
587
  return obj;
478
588
  }
479
589
 
480
- Napi::Object CallData::PopArray(const uint8_t *src, const TypeInfo *type, int16_t realign)
590
+ Size WideStringLength(const char16_t *str16, Size max)
591
+ {
592
+ Size len = 0;
593
+
594
+ while (len < max && str16[len]) {
595
+ len++;
596
+ }
597
+
598
+ return len;
599
+ }
600
+
601
+ Napi::Value CallData::PopArray(const uint8_t *src, const TypeInfo *type, int16_t realign)
481
602
  {
482
603
  RG_ASSERT(type->primitive == PrimitiveKind::Array);
483
604
 
@@ -500,7 +621,12 @@ Napi::Object CallData::PopArray(const uint8_t *src, const TypeInfo *type, int16_
500
621
  } while (false)
501
622
  #define POP_NUMBER_ARRAY(TypedArrayType, CType) \
502
623
  do { \
503
- if (type->hint == TypeInfo::ArrayHint::TypedArray) { \
624
+ if (type->hint == TypeInfo::ArrayHint::Array) { \
625
+ POP_ARRAY({ \
626
+ double d = (double)*(CType *)src; \
627
+ array.Set(i, Napi::Number::New(env, d)); \
628
+ }); \
629
+ } else { \
504
630
  Napi::TypedArrayType array = Napi::TypedArrayType::New(env, len); \
505
631
  \
506
632
  for (uint32_t i = 0; i < len; i++) { \
@@ -514,11 +640,6 @@ Napi::Object CallData::PopArray(const uint8_t *src, const TypeInfo *type, int16_
514
640
  } \
515
641
  \
516
642
  return array; \
517
- } else { \
518
- POP_ARRAY({ \
519
- double d = (double)*(CType *)src; \
520
- array.Set(i, Napi::Number::New(env, d)); \
521
- }); \
522
643
  } \
523
644
  } while (false)
524
645
 
@@ -531,10 +652,58 @@ Napi::Object CallData::PopArray(const uint8_t *src, const TypeInfo *type, int16_
531
652
  array.Set(i, Napi::Boolean::New(env, b));
532
653
  });
533
654
  } break;
534
- case PrimitiveKind::Int8: { POP_NUMBER_ARRAY(Int8Array, int8_t); } break;
535
- case PrimitiveKind::UInt8: { POP_NUMBER_ARRAY(Uint8Array, uint8_t); } break;
536
- case PrimitiveKind::Int16: { POP_NUMBER_ARRAY(Int16Array, int16_t); } break;
537
- case PrimitiveKind::UInt16: { POP_NUMBER_ARRAY(Uint16Array, uint16_t); } break;
655
+ case PrimitiveKind::Int8: {
656
+ if (type->hint == TypeInfo::ArrayHint::String) {
657
+ RG_ASSERT(!realign);
658
+
659
+ const char *ptr = (const char *)src;
660
+ size_t count = strnlen(ptr, (size_t)len);
661
+
662
+ Napi::String str = Napi::String::New(env, ptr, count);
663
+ return str;
664
+ }
665
+
666
+ POP_NUMBER_ARRAY(Int8Array, int8_t);
667
+ } break;
668
+ case PrimitiveKind::UInt8: {
669
+ if (type->hint == TypeInfo::ArrayHint::String) {
670
+ RG_ASSERT(!realign);
671
+
672
+ const char *ptr = (const char *)src;
673
+ size_t count = strnlen(ptr, (size_t)len);
674
+
675
+ Napi::String str = Napi::String::New(env, ptr, count);
676
+ return str;
677
+ }
678
+
679
+ POP_NUMBER_ARRAY(Uint8Array, uint8_t);
680
+ } break;
681
+ case PrimitiveKind::Int16: {
682
+ if (type->hint == TypeInfo::ArrayHint::String) {
683
+ RG_ASSERT(!realign);
684
+
685
+ const char16_t *ptr = (const char16_t *)src;
686
+ Size count = WideStringLength(ptr, len);
687
+
688
+ Napi::String str = Napi::String::New(env, ptr, count);
689
+ return str;
690
+ }
691
+
692
+ POP_NUMBER_ARRAY(Int16Array, int16_t);
693
+ } break;
694
+ case PrimitiveKind::UInt16: {
695
+ if (type->hint == TypeInfo::ArrayHint::String) {
696
+ RG_ASSERT(!realign);
697
+
698
+ const char16_t *ptr = (const char16_t *)src;
699
+ Size count = WideStringLength(ptr, len);
700
+
701
+ Napi::String str = Napi::String::New(env, ptr, count);
702
+ return str;
703
+ }
704
+
705
+ POP_NUMBER_ARRAY(Uint16Array, uint16_t);
706
+ } break;
538
707
  case PrimitiveKind::Int32: { POP_NUMBER_ARRAY(Int32Array, int32_t); } break;
539
708
  case PrimitiveKind::UInt32: { POP_NUMBER_ARRAY(Uint32Array, uint32_t); } break;
540
709
  case PrimitiveKind::Int64: {
@@ -579,8 +748,8 @@ Napi::Object CallData::PopArray(const uint8_t *src, const TypeInfo *type, int16_
579
748
  } break;
580
749
  case PrimitiveKind::Array: {
581
750
  POP_ARRAY({
582
- Napi::Object obj = PopArray(src, type->ref, realign);
583
- array.Set(i, obj);
751
+ Napi::Value value = PopArray(src, type->ref, realign);
752
+ array.Set(i, value);
584
753
  });
585
754
  } break;
586
755
  case PrimitiveKind::Float32: { POP_NUMBER_ARRAY(Float32Array, float); } break;
package/src/call.hh CHANGED
@@ -78,11 +78,11 @@ private:
78
78
  const char *PushString(const Napi::Value &value);
79
79
  const char16_t *PushString16(const Napi::Value &value);
80
80
  bool PushObject(const Napi::Object &obj, const TypeInfo *type, uint8_t *dest, int16_t realign = 0);
81
- bool PushArray(const Napi::Object &obj, const TypeInfo *type, uint8_t *dest, int16_t realign = 0);
81
+ bool PushArray(const Napi::Value &obj, const TypeInfo *type, uint8_t *dest, int16_t realign = 0);
82
82
 
83
83
  void PopObject(Napi::Object obj, const uint8_t *src, const TypeInfo *type, int16_t realign = 0);
84
84
  Napi::Object PopObject(const uint8_t *src, const TypeInfo *type, int16_t realign = 0);
85
- Napi::Object PopArray(const uint8_t *src, const TypeInfo *type, int16_t realign = 0);
85
+ Napi::Value PopArray(const uint8_t *src, const TypeInfo *type, int16_t realign = 0);
86
86
  };
87
87
 
88
88
  template <typename T>
package/src/ffi.cc CHANGED
@@ -274,6 +274,8 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
274
274
  hint = TypeInfo::ArrayHint::TypedArray;
275
275
  } else if (to == "array") {
276
276
  hint = TypeInfo::ArrayHint::Array;
277
+ } else if (to == "string") {
278
+ hint = TypeInfo::ArrayHint::String;
277
279
  } else {
278
280
  ThrowError<Napi::Error>(env, "Array conversion hint must be 'typed' or 'array'");
279
281
  return env.Null();
@@ -297,7 +299,6 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
297
299
  }
298
300
 
299
301
  TypeInfo *type = instance->types.AppendDefault();
300
- RG_DEFER_N(err_guard) { instance->types.RemoveLast(1); };
301
302
 
302
303
  type->name = Fmt(&instance->str_alloc, "%1[%2]", ref->name, len).ptr;
303
304
 
@@ -307,13 +308,6 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
307
308
  type->ref = ref;
308
309
  type->hint = hint;
309
310
 
310
- // If the insert succeeds, we cannot fail anymore
311
- if (!instance->types_map.TrySet(type).second) {
312
- ThrowError<Napi::Error>(env, "Duplicate type name '%1'", type->name);
313
- return env.Null();
314
- }
315
- err_guard.Disable();
316
-
317
311
  Napi::External<TypeInfo> external = Napi::External<TypeInfo>::New(env, type);
318
312
  SetValueTag(instance, external, &TypeInfoMarker);
319
313
 
@@ -390,9 +384,11 @@ static Span<uint8_t> AllocateAndAlign16(Allocator *alloc, Size size)
390
384
  return MakeSpan(aligned, size - delta);
391
385
  }
392
386
 
393
- static InstanceMemory *AllocateCallMemory(InstanceData *instance)
387
+ static InstanceMemory *AllocateAsyncMemory(InstanceData *instance)
394
388
  {
395
- for (InstanceMemory *mem: instance->memories) {
389
+ for (Size i = 1; i < instance->memories.len; i++) {
390
+ InstanceMemory *mem = instance->memories[i];
391
+
396
392
  if (!mem->depth)
397
393
  return mem;
398
394
  }
@@ -422,7 +418,7 @@ static Napi::Value TranslateNormalCall(const Napi::CallbackInfo &info)
422
418
  return env.Null();
423
419
  }
424
420
 
425
- InstanceMemory *mem = AllocateCallMemory(instance);
421
+ InstanceMemory *mem = instance->memories[0];
426
422
  CallData call(env, instance, func, mem);
427
423
 
428
424
  return call.Run(info);
@@ -482,7 +478,7 @@ static Napi::Value TranslateVariadicCall(const Napi::CallbackInfo &info)
482
478
  if (RG_UNLIKELY(!AnalyseFunction(instance, &func)))
483
479
  return env.Null();
484
480
 
485
- InstanceMemory *mem = AllocateCallMemory(instance);
481
+ InstanceMemory *mem = instance->memories[0];
486
482
  CallData call(env, instance, &func, mem);
487
483
 
488
484
  return call.Run(info);
@@ -558,7 +554,7 @@ static Napi::Value TranslateAsyncCall(const Napi::CallbackInfo &info)
558
554
  return env.Null();
559
555
  }
560
556
 
561
- InstanceMemory *mem = AllocateCallMemory(instance);
557
+ InstanceMemory *mem = AllocateAsyncMemory(instance);
562
558
  AsyncCall *async = new AsyncCall(env, instance, func, mem, callback);
563
559
 
564
560
  if (async->Prepare(info) && instance->debug) {
@@ -657,9 +653,13 @@ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConve
657
653
  if (!ParseClassicFunction(env, info[0u].As<Napi::String>(), info[1u], info[2u].As<Napi::Array>(), func))
658
654
  return env.Null();
659
655
  } else if (info.Length() >= 1) {
660
- PrototypeParser parser(env);
656
+ if (!info[0].IsString()) {
657
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for prototype, expected string", GetValueType(instance, info[0]));
658
+ return env.Null();
659
+ }
661
660
 
662
- if (!parser.Parse(info[0u].As<Napi::String>(), func))
661
+ std::string proto = info[0u].As<Napi::String>();
662
+ if (!ParsePrototype(env, proto.c_str(), func))
663
663
  return env.Null();
664
664
  } else {
665
665
  ThrowError<Napi::TypeError>(env, "Expected 1 or 3 arguments, not %1", info.Length());
@@ -930,8 +930,12 @@ void FunctionInfo::Unref() const
930
930
 
931
931
  InstanceData::InstanceData()
932
932
  {
933
- AllocateCallMemory(this);
934
- RG_ASSERT(memories.len == 1);
933
+ InstanceMemory *mem = new InstanceMemory();
934
+
935
+ mem->stack = AllocateAndAlign16(&mem->mem_alloc, Mebibytes(2));
936
+ mem->heap = AllocateAndAlign16(&mem->mem_alloc, Mebibytes(4));
937
+
938
+ memories.Append(mem);
935
939
  }
936
940
 
937
941
  InstanceData::~InstanceData()
@@ -957,7 +961,6 @@ static void SetExports(Napi::Env env, Func func)
957
961
  func("out", Napi::Function::New(env, MarkOut));
958
962
  func("inout", Napi::Function::New(env, MarkInOut));
959
963
 
960
- func("internal", Napi::Boolean::New(env, true));
961
964
  #if defined(_WIN32)
962
965
  func("extension", Napi::String::New(env, ".dll"));
963
966
  #elif defined(__APPLE__)
@@ -1009,6 +1012,7 @@ static void InitInternal(v8::Local<v8::Object> target, v8::Local<v8::Value>,
1009
1012
  FillRandomSafe(&instance->tag_lower, RG_SIZE(instance->tag_lower));
1010
1013
 
1011
1014
  SetExports(env_napi, [&](const char *name, Napi::Value value) { SetValue(env, target, name, value); });
1015
+ SetValue(env, target, "internal", Napi::Boolean::New(env_cxx, true));
1012
1016
  }
1013
1017
 
1014
1018
  #else
@@ -1024,6 +1028,7 @@ static Napi::Object InitModule(Napi::Env env, Napi::Object exports)
1024
1028
  FillRandomSafe(&instance->tag_lower, RG_SIZE(instance->tag_lower));
1025
1029
 
1026
1030
  SetExports(env, [&](const char *name, Napi::Value value) { exports.Set(name, value); });
1031
+ exports.Set("internal", Napi::Boolean::New(env, false));
1027
1032
 
1028
1033
  return exports;
1029
1034
  }
package/src/ffi.hh CHANGED
@@ -69,7 +69,8 @@ struct RecordMember;
69
69
  struct TypeInfo {
70
70
  enum class ArrayHint {
71
71
  Array,
72
- TypedArray
72
+ TypedArray,
73
+ String
73
74
  };
74
75
 
75
76
  const char *name;
@@ -189,7 +190,7 @@ struct InstanceData {
189
190
  bool debug;
190
191
  uint64_t tag_lower;
191
192
 
192
- LocalArray<InstanceMemory *, 8> memories;
193
+ LocalArray<InstanceMemory *, 6> memories;
193
194
 
194
195
  BlockAllocator str_alloc;
195
196
  };
package/src/parser.cc CHANGED
@@ -19,34 +19,27 @@
19
19
 
20
20
  namespace RG {
21
21
 
22
- bool PrototypeParser::Parse(Napi::String proto, FunctionInfo *func)
22
+ bool PrototypeParser::Parse(const char *str, FunctionInfo *out_func)
23
23
  {
24
- if (!proto.IsString()) {
25
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value for prototype, expected string", GetValueType(instance, proto));
26
- return false;
27
- }
28
-
29
- std::string hold = proto;
30
-
31
24
  tokens.Clear();
32
25
  offset = 0;
33
26
  valid = true;
34
27
 
35
- Tokenize(hold.c_str());
28
+ Tokenize(str);
36
29
 
37
- func->ret.type = ParseType();
38
- if (func->ret.type->primitive == PrimitiveKind::Array) {
30
+ out_func->ret.type = ParseType();
31
+ if (out_func->ret.type->primitive == PrimitiveKind::Array) {
39
32
  MarkError("You are not allowed to directly return C arrays");
40
33
  return false;
41
34
  }
42
35
  if (Match("__cdecl")) {
43
- func->convention = CallConvention::Cdecl;
36
+ out_func->convention = CallConvention::Cdecl;
44
37
  } else if (Match("__stdcall")) {
45
- func->convention = CallConvention::Stdcall;
38
+ out_func->convention = CallConvention::Stdcall;
46
39
  } else if (Match("__fastcall")) {
47
- func->convention = CallConvention::Fastcall;
40
+ out_func->convention = CallConvention::Fastcall;
48
41
  }
49
- func->name = ParseIdentifier();
42
+ out_func->name = ParseIdentifier();
50
43
 
51
44
  Consume("(");
52
45
  if (offset < tokens.len && tokens[offset] != ")") {
@@ -54,7 +47,7 @@ bool PrototypeParser::Parse(Napi::String proto, FunctionInfo *func)
54
47
  ParameterInfo param = {};
55
48
 
56
49
  if (Match("...")) {
57
- func->variadic = true;
50
+ out_func->variadic = true;
58
51
  break;
59
52
  }
60
53
 
@@ -83,18 +76,18 @@ bool PrototypeParser::Parse(Napi::String proto, FunctionInfo *func)
83
76
 
84
77
  offset += (offset < tokens.len && IsIdentifier(tokens[offset]));
85
78
 
86
- if (func->parameters.len >= MaxParameters) {
79
+ if (out_func->parameters.len >= MaxParameters) {
87
80
  MarkError("Functions cannot have more than %1 parameters", MaxParameters);
88
81
  return false;
89
82
  }
90
- if ((param.directions & 2) && ++func->out_parameters >= MaxOutParameters) {
83
+ if ((param.directions & 2) && ++out_func->out_parameters >= MaxOutParameters) {
91
84
  MarkError("Functions cannot have more than out %1 parameters", MaxOutParameters);
92
85
  return false;
93
86
  }
94
87
 
95
- param.offset = func->parameters.len;
88
+ param.offset = out_func->parameters.len;
96
89
 
97
- func->parameters.Append(param);
90
+ out_func->parameters.Append(param);
98
91
 
99
92
  if (offset >= tokens.len || tokens[offset] != ",")
100
93
  break;
@@ -125,6 +118,17 @@ void PrototypeParser::Tokenize(const char *str)
125
118
  Span<const char> tok = MakeSpan(str + i, j - i);
126
119
  tokens.Append(tok);
127
120
 
121
+ i = j - 1;
122
+ } else if (IsAsciiDigit(c)) {
123
+ Size j = i;
124
+ while (str[++j] && IsAsciiDigit(str[j]));
125
+ if (str[j] == '.') {
126
+ while (str[++j] && IsAsciiDigit(str[j]));
127
+ }
128
+
129
+ Span<const char> tok = MakeSpan(str + i, j - i);
130
+ tokens.Append(tok);
131
+
128
132
  i = j - 1;
129
133
  } else if (c == '.' && str[i + 1] == '.' && str[i + 2] == '.') {
130
134
  tokens.Append("...");
@@ -250,4 +254,10 @@ bool PrototypeParser::IsIdentifier(Span<const char> tok) const
250
254
  return IsAsciiAlpha(tok[0]) || tok[0] == '_';
251
255
  }
252
256
 
257
+ bool ParsePrototype(Napi::Env env, const char *str, FunctionInfo *out_func)
258
+ {
259
+ PrototypeParser parser(env);
260
+ return parser.Parse(str, out_func);
261
+ }
262
+
253
263
  }
package/src/parser.hh CHANGED
@@ -36,7 +36,7 @@ class PrototypeParser {
36
36
  public:
37
37
  PrototypeParser(Napi::Env env) : env(env), instance(env.GetInstanceData<InstanceData>()) {}
38
38
 
39
- bool Parse(Napi::String proto, FunctionInfo *func);
39
+ bool Parse(const char *str, FunctionInfo *out_func);
40
40
 
41
41
  private:
42
42
  void Tokenize(const char *str);
@@ -60,4 +60,6 @@ private:
60
60
  }
61
61
  };
62
62
 
63
+ bool ParsePrototype(Napi::Env env, const char *str, FunctionInfo *out_func);
64
+
63
65
  }
package/src/util.hh CHANGED
@@ -78,7 +78,7 @@ T CopyNumber(const Napi::Value &value)
78
78
  RG_ASSERT(value.IsNumber() || value.IsBigInt());
79
79
 
80
80
  if (RG_LIKELY(value.IsNumber())) {
81
- return (T)value.As<Napi::Number>();
81
+ return (T)value.As<Napi::Number>().DoubleValue();
82
82
  } else if (value.IsBigInt()) {
83
83
  Napi::BigInt bigint = value.As<Napi::BigInt>();
84
84
 
package/test/misc.c CHANGED
@@ -104,6 +104,13 @@ typedef struct PackedBFG {
104
104
  } PackedBFG;
105
105
  #pragma pack(pop)
106
106
 
107
+ typedef struct FixedString {
108
+ char buf[64];
109
+ } FixedString;
110
+ typedef struct FixedWide {
111
+ int16_t buf[64];
112
+ } FixedWide;
113
+
107
114
  EXPORT void FillPack1(int a, Pack1 *p)
108
115
  {
109
116
  p->a = a;
@@ -359,3 +366,13 @@ EXPORT const char16_t *Concat16(const char16_t *str1, const char16_t *str2)
359
366
 
360
367
  return buf;
361
368
  }
369
+
370
+ EXPORT FixedString ReturnFixedStr(FixedString str)
371
+ {
372
+ return str;
373
+ }
374
+
375
+ EXPORT FixedWide ReturnFixedWide(FixedWide str)
376
+ {
377
+ return str;
378
+ }