koffi 3.0.2 → 3.1.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +32 -7
  2. package/cnoke.cjs +2 -2
  3. package/doc/benchmarks.md +2 -2
  4. package/doc/callbacks.md +8 -27
  5. package/doc/{input.md → composites.md} +161 -147
  6. package/doc/contribute.md +2 -1
  7. package/doc/index.md +0 -14
  8. package/doc/{functions.md → load.md} +54 -113
  9. package/doc/migration.md +4 -7
  10. package/doc/misc.md +0 -103
  11. package/doc/output.md +5 -11
  12. package/doc/pointers.md +76 -17
  13. package/doc/primitives.md +151 -0
  14. package/doc/start.md +3 -13
  15. package/doc/types.md +88 -0
  16. package/doc/unions.md +0 -186
  17. package/doc/values.md +134 -0
  18. package/index.d.ts +23 -4
  19. package/lib/native/base/base.cc +142 -90
  20. package/lib/native/base/base.hh +69 -165
  21. package/package.json +16 -15
  22. package/src/koffi/CMakeLists.txt +19 -17
  23. package/src/koffi/index.cjs +8 -3
  24. package/src/koffi/index.js +1 -1
  25. package/src/koffi/indirect.cjs +8 -3
  26. package/src/koffi/indirect.js +1 -1
  27. package/src/koffi/src/abi/arm64.cc +47 -62
  28. package/src/koffi/src/abi/riscv64.cc +38 -57
  29. package/src/koffi/src/abi/x64sysv.cc +38 -57
  30. package/src/koffi/src/abi/x64win.cc +47 -65
  31. package/src/koffi/src/abi/x86.cc +46 -59
  32. package/src/koffi/src/call.cc +239 -133
  33. package/src/koffi/src/call.hh +5 -10
  34. package/src/koffi/src/ffi.cc +211 -90
  35. package/src/koffi/src/ffi.hh +36 -16
  36. package/src/koffi/src/parser.cc +3 -3
  37. package/src/koffi/src/parser.hh +2 -2
  38. package/src/koffi/src/static.cjs +8 -0
  39. package/src/koffi/src/static.js +11 -0
  40. package/src/koffi/src/type.cc +43 -33
  41. package/src/koffi/src/type.hh +1 -1
  42. package/src/koffi/src/util.cc +73 -173
  43. package/src/koffi/src/util.hh +84 -43
  44. package/src/koffi/src/uv.cc +1 -1
  45. package/vendor/node-addon-api/README.md +1 -1
  46. package/vendor/node-addon-api/napi-inl.h +213 -35
  47. package/vendor/node-addon-api/napi.h +118 -7
  48. package/doc/variables.md +0 -102
@@ -36,8 +36,8 @@ namespace K {
36
36
 
37
37
  #define NAPI_OK(Call) \
38
38
  do { \
39
- napi_status status = (Call); \
40
- K_ASSERT(status == napi_ok); \
39
+ napi_status _status = (Call); \
40
+ K_ASSERT(_status == napi_ok); \
41
41
  } while (false)
42
42
 
43
43
  static const Size DefaultSyncStackSize = Mebibytes(1);
@@ -67,7 +67,7 @@ struct RecordMember;
67
67
  struct FunctionInfo;
68
68
  struct CallData;
69
69
 
70
- typedef void DisposeFunc (Napi::Env env, const TypeInfo *type, const void *ptr);
70
+ typedef void DisposeFunc (InstanceData *instance, const TypeInfo *type, const void *ptr);
71
71
 
72
72
  enum class TypeFlag {
73
73
  HasTypedArray = 1 << 0,
@@ -90,22 +90,30 @@ static const char *const ArrayHintNames[] = {
90
90
 
91
91
  struct RecordMember {
92
92
  const char *name;
93
- napi_ref key;
94
93
  const TypeInfo *type;
95
94
  int32_t offset;
96
95
  Size countedby;
96
+
97
+ napi_ref key = nullptr;
98
+
99
+ #if defined(K_DEBUG)
100
+ ~RecordMember() { K_ASSERT(!key); }
101
+ #endif
97
102
  };
98
103
 
99
104
  struct TypeInfo {
100
- const char *name;
105
+ K_DELETE_COPY(TypeInfo)
106
+
107
+ InstanceData *instance = nullptr;
101
108
 
109
+ const char *name;
102
110
  alignas(8) PrimitiveKind primitive;
103
111
  int32_t size;
104
112
  int16_t align;
105
113
  uint16_t flags;
106
114
 
107
- DisposeFunc *dispose;
108
- Napi::FunctionReference dispose_ref;
115
+ DisposeFunc *dispose = nullptr;
116
+ napi_ref dispose_ref = nullptr;
109
117
 
110
118
  HeapArray<RecordMember> members; // Record or Union
111
119
  struct {
@@ -116,15 +124,23 @@ struct TypeInfo {
116
124
  ArrayHint hint; // Array only
117
125
  const char *countedby; // Pointer or array
118
126
 
119
- mutable Napi::FunctionReference construct; // Union only
120
- mutable Napi::ObjectReference defn;
127
+ mutable napi_ref construct = nullptr; // Union only
128
+ mutable napi_ref defn = nullptr;
129
+
130
+ mutable const TypeInfo *reshaped = nullptr;
121
131
 
122
- mutable const TypeInfo *reshaped;
132
+ TypeInfo() = default;
133
+ ~TypeInfo();
134
+
135
+ TypeInfo(TypeInfo &&other) = default;
136
+ TypeInfo &operator=(TypeInfo &&other) = default;
123
137
 
124
138
  K_HASHTABLE_HANDLER(TypeInfo, name);
125
139
  };
126
140
 
127
141
  struct LibraryHolder {
142
+ K_DELETE_COPY(LibraryHolder)
143
+
128
144
  void *module = nullptr; // HMODULE on Windows
129
145
  mutable int refcount = 1;
130
146
 
@@ -196,7 +212,9 @@ struct ParameterInfo {
196
212
  // ABI-specific part
197
213
 
198
214
  #if defined(_M_X64)
199
- bool regular; // Used for structs and unions
215
+ struct {
216
+ bool regular; // Used for structs and unions
217
+ } abi;
200
218
  #elif defined(__x86_64__)
201
219
  struct {
202
220
  bool regular;
@@ -222,7 +240,9 @@ struct ReturnInfo {
222
240
  // ABI-specific part
223
241
 
224
242
  #if defined(_M_X64)
225
- bool regular;
243
+ struct {
244
+ bool regular;
245
+ } abi;
226
246
  #elif defined(__x86_64__)
227
247
  struct {
228
248
  AbiMethod method;
@@ -233,7 +253,9 @@ struct ReturnInfo {
233
253
  int offset;
234
254
  } abi;
235
255
  #elif defined(__i386__) || defined(_M_IX86)
236
- bool trivial;
256
+ struct {
257
+ bool regular;
258
+ } abi;
237
259
  #elif __riscv_xlen == 64 || defined(__loongarch64)
238
260
  struct {
239
261
  AbiMethod method;
@@ -352,8 +374,6 @@ struct InstanceData {
352
374
  // Variadic cache
353
375
  FunctionInfo *variadic_func = nullptr;
354
376
 
355
- napi_value (*translate_zero_call)(napi_env env, napi_callback_info info);
356
-
357
377
  std::thread::id main_thread_id;
358
378
  napi_threadsafe_function broker = nullptr;
359
379
 
@@ -428,12 +448,12 @@ extern const napi_type_tag CastMarker;
428
448
  extern SharedData shared;
429
449
 
430
450
  // Some Node-API functions are loaded dynamically to work around bugs or because they are recent
451
+ extern napi_status (NAPI_CDECL *node_api_delete_reference)(node_api_basic_env env, napi_ref ref);
431
452
  extern napi_status (NAPI_CDECL *node_api_get_buffer_info)(napi_env env, napi_value value, void **data, size_t *length);
432
453
  extern napi_status (NAPI_CDECL *node_api_create_property_key_utf8)(napi_env env, const char* str, size_t length, napi_value* result);
433
454
  extern napi_status (NAPI_CDECL *node_api_post_finalizer)(node_api_basic_env env, napi_finalize finalize_cb, void* finalize_data, void* finalize_hint);
434
455
  extern napi_status (NAPI_CDECL *node_api_create_object_with_properties)(napi_env env, napi_value prototype_or_null, const napi_value *property_names,
435
456
  const napi_value *property_values, size_t property_count, napi_value *result);
436
- extern napi_value (*translate_zero_call)(napi_env env, napi_callback_info info);
437
457
 
438
458
  InstanceMemory *AllocateMemory(InstanceData *instance, Size stack_size, Size heap_size);
439
459
  void ReleaseMemory(InstanceData *instance, InstanceMemory *mem);
@@ -2,10 +2,10 @@
2
2
  // SPDX-FileCopyrightText: 2026 Niels Martignène <niels.martignene@protonmail.com>
3
3
 
4
4
  #include "lib/native/base/base.hh"
5
+ #include "call.hh"
5
6
  #include "ffi.hh"
6
7
  #include "parser.hh"
7
8
  #include "type.hh"
8
- #include "util.hh"
9
9
 
10
10
  #include <napi.h>
11
11
 
@@ -209,9 +209,9 @@ bool PrototypeParser::IsIdentifier(Span<const char> tok) const
209
209
  return IsAsciiAlpha(tok[0]) || tok[0] == '_';
210
210
  }
211
211
 
212
- bool ParsePrototype(Napi::Env env, const char *str, bool concrete, FunctionInfo *out_func)
212
+ bool ParsePrototype(InstanceData *instance, const char *str, bool concrete, FunctionInfo *out_func)
213
213
  {
214
- PrototypeParser parser(env);
214
+ PrototypeParser parser(instance);
215
215
  return parser.Parse(str, concrete, out_func);
216
216
  }
217
217
 
@@ -24,7 +24,7 @@ class PrototypeParser {
24
24
  bool valid;
25
25
 
26
26
  public:
27
- PrototypeParser(Napi::Env env) : env(env), instance(env.GetInstanceData<InstanceData>()) {}
27
+ PrototypeParser(InstanceData *instance) : env(instance->env), instance(instance) {}
28
28
 
29
29
  bool Parse(const char *str, bool concrete, FunctionInfo *out_func);
30
30
 
@@ -49,6 +49,6 @@ private:
49
49
  }
50
50
  };
51
51
 
52
- bool ParsePrototype(Napi::Env env, const char *str, bool concrete, FunctionInfo *out_func);
52
+ bool ParsePrototype(InstanceData *instance, const char *str, bool concrete, FunctionInfo *out_func);
53
53
 
54
54
  }
@@ -92,6 +92,14 @@ function loadStatic(pkg) {
92
92
  }
93
93
  }
94
94
 
95
+ if (pkg == 'win32-arm64') {
96
+ try {
97
+ native = require('@koromix/koffi-win32-arm64');
98
+ } catch (err) {
99
+ // Go on
100
+ }
101
+ }
102
+
95
103
  if (pkg == 'darwin-x64') {
96
104
  try {
97
105
  native = require('@koromix/koffi-darwin-x64');
@@ -1,6 +1,9 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // SPDX-FileCopyrightText: 2026 Niels Martignène <niels.martignene@protonmail.com>
3
3
 
4
+ import { createRequire } from "node:module";
5
+ const require = createRequire(import.meta.url);
6
+
4
7
  function loadStatic(pkg) {
5
8
  let native = null;
6
9
 
@@ -92,6 +95,14 @@ function loadStatic(pkg) {
92
95
  }
93
96
  }
94
97
 
98
+ if (pkg == 'win32-arm64') {
99
+ try {
100
+ native = require('@koromix/koffi-win32-arm64');
101
+ } catch (err) {
102
+ // Go on
103
+ }
104
+ }
105
+
95
106
  if (pkg == 'darwin-x64') {
96
107
  try {
97
108
  native = require('@koromix/koffi-darwin-x64');
@@ -2,9 +2,9 @@
2
2
  // SPDX-FileCopyrightText: 2026 Niels Martignène <niels.martignene@protonmail.com>
3
3
 
4
4
  #include "lib/native/base/base.hh"
5
+ #include "call.hh"
5
6
  #include "ffi.hh"
6
7
  #include "type.hh"
7
- #include "util.hh"
8
8
 
9
9
  #include <napi.h>
10
10
 
@@ -36,7 +36,7 @@ TypeObject::TypeObject(const Napi::CallbackInfo &info)
36
36
 
37
37
  void TypeObject::Finalize(Napi::BasicEnv env)
38
38
  {
39
- DeleteReferenceSafe(env, *this);
39
+ node_api_delete_reference(env, *this);
40
40
  SuppressDestruct();
41
41
  }
42
42
 
@@ -306,15 +306,12 @@ const TypeInfo *ResolveType(InstanceData *instance, Span<const char> str)
306
306
 
307
307
  memcpy((void *)copy, (const void *)type, K_SIZE(*type));
308
308
  copy->name = Fmt(&instance->str_alloc, "<anonymous_%1>", instance->types.count).ptr;
309
- copy->members.allocator = GetNullAllocator();
310
- copy->members.allocator = GetNullAllocator();
311
- memset((void *)&copy->defn, 0, K_SIZE(copy->defn));
309
+ copy->defn = nullptr;
310
+ K_ASSERT(!type->members.len);
312
311
 
313
312
  static_assert(!std::is_polymorphic_v<Napi::ObjectReference>);
314
313
 
315
- copy->dispose = [](Napi::Env env, const TypeInfo *, const void *ptr) {
316
- InstanceData *instance = env.GetInstanceData<InstanceData>();
317
-
314
+ copy->dispose = [](InstanceData *instance, const TypeInfo *, const void *ptr) {
318
315
  free((void *)ptr);
319
316
  instance->stats.disposed++;
320
317
  };
@@ -436,27 +433,27 @@ TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size len, A
436
433
  return MakeArrayType(instance, ref, len, hint, false);
437
434
  }
438
435
 
439
- napi_value WrapType(Napi::Env env, const TypeInfo *type, bool freeze)
436
+ napi_value WrapType(InstanceData *instance, const TypeInfo *type, bool freeze)
440
437
  {
441
- if (type->defn.IsEmpty()) {
438
+ Napi::Env env = instance->env;
439
+
440
+ if (!type->defn) {
442
441
  #if defined(EXTERNAL_TYPES)
443
442
  Napi::Object defn = Napi::Object::New(env);
444
443
  #else
445
- InstanceData *instance = env.GetInstanceData<InstanceData>();
446
-
447
444
  Napi::External<TypeInfo> external = Napi::External<TypeInfo>::New(env, (TypeInfo *)type);
448
445
  Napi::Object defn = instance->construct_type.New({ external });
449
446
  SetValueTag(env, defn, &TypeObjectMarker);
450
447
  #endif
451
448
 
452
- defn.Set("name", Napi::String::New(env, type->name));
449
+ defn.Set("name", NewString(env, type->name));
453
450
  defn.Set("primitive", PrimitiveKindNames[(int)type->primitive]);
454
- defn.Set("size", Napi::Number::New(env, (double)type->size));
455
- defn.Set("alignment", Napi::Number::New(env, (double)type->align));
451
+ defn.Set("size", NewInt(env, type->size));
452
+ defn.Set("alignment", NewInt(env, type->align));
456
453
  defn.Set("disposable", Napi::Boolean::New(env, !!type->dispose));
457
454
 
458
455
  // Assign before to avoid possible recursion crash
459
- type->defn = Napi::Persistent(defn);
456
+ NAPI_OK(napi_create_reference(env, defn, 1, &type->defn));
460
457
 
461
458
  switch (type->primitive) {
462
459
  case PrimitiveKind::Void:
@@ -483,12 +480,12 @@ napi_value WrapType(Napi::Env env, const TypeInfo *type, bool freeze)
483
480
 
484
481
  case PrimitiveKind::Array: {
485
482
  uint32_t len = type->size / type->ref.type->size;
486
- defn.Set("length", Napi::Number::New(env, (double)len));
483
+ defn.Set("length", NewInt(env, len));
487
484
  defn.Set("hint", ArrayHintNames[(int)type->hint]);
488
485
  } [[fallthrough]];
489
486
  case PrimitiveKind::Pointer: {
490
- napi_value value = WrapType(env, type->ref.type);
491
- defn.Set("ref", value);
487
+ napi_value ref = WrapType(instance, type->ref.type);
488
+ defn.Set("ref", ref);
492
489
  } break;
493
490
  case PrimitiveKind::Record:
494
491
  case PrimitiveKind::Union: {
@@ -498,7 +495,7 @@ napi_value WrapType(Napi::Env env, const TypeInfo *type, bool freeze)
498
495
  Napi::Object obj = Napi::Object::New(env);
499
496
 
500
497
  obj.Set("name", member.name);
501
- obj.Set("type", WrapType(env, member.type));
498
+ obj.Set("type", WrapType(instance, member.type));
502
499
  obj.Set("offset", member.offset);
503
500
 
504
501
  members.Set(member.name, obj);
@@ -510,7 +507,7 @@ napi_value WrapType(Napi::Env env, const TypeInfo *type, bool freeze)
510
507
 
511
508
  case PrimitiveKind::Prototype:
512
509
  case PrimitiveKind::Callback: {
513
- napi_value meta = DescribeFunction(env, type->proto);
510
+ napi_value meta = DescribeFunction(instance, type->proto);
514
511
  defn.Set("proto", meta);
515
512
  } break;
516
513
  }
@@ -526,19 +523,27 @@ napi_value WrapType(Napi::Env env, const TypeInfo *type, bool freeze)
526
523
 
527
524
  return external;
528
525
  #else
529
- return type->defn.Value();
526
+ napi_value defn;
527
+ NAPI_OK(napi_get_reference_value(env, type->defn, &defn));
528
+
529
+ return defn;
530
530
  #endif
531
531
  }
532
532
 
533
533
  const TypeInfo *ReshapeType(InstanceData *instance, const TypeInfo *type, int32_t stride, uint16_t flags)
534
534
  {
535
- K_ASSERT(!type->defn.IsEmpty());
535
+ K_ASSERT(type->defn);
536
536
 
537
537
  if (!type->reshaped) {
538
+ Napi::Env env = instance->env;
539
+
538
540
  TypeInfo *reshaped = nullptr;
539
541
 
540
542
  switch (type->primitive) {
541
543
  case PrimitiveKind::Record: {
544
+ napi_value defn;
545
+ NAPI_OK(napi_get_reference_value(env, type->defn, &defn));
546
+
542
547
  reshaped = instance->types.AppendDefault();
543
548
 
544
549
  memcpy((void *)reshaped, (const void *)type, K_SIZE(*type));
@@ -546,31 +551,36 @@ const TypeInfo *ReshapeType(InstanceData *instance, const TypeInfo *type, int32_
546
551
  reshaped->members.Reserve(type->members.len);
547
552
  reshaped->size = 0;
548
553
  reshaped->flags |= flags;
549
- memset((void *)&reshaped->defn, 0, K_SIZE(reshaped->defn));
550
-
551
- Napi::Object defn = type->defn.Value();
552
- reshaped->defn = Napi::Persistent(defn);
554
+ NAPI_OK(napi_create_reference(env, defn, 1, &reshaped->defn));
553
555
 
554
556
  for (RecordMember member: type->members) {
555
557
  member.offset = reshaped->size;
556
558
  member.type = ReshapeType(instance, member.type, stride, flags);
557
559
 
560
+ if (member.key) {
561
+ napi_value key;
562
+ NAPI_OK(napi_get_reference_value(env, member.key, &key));
563
+ NAPI_OK(napi_create_reference(env, key, 1, &member.key));
564
+ }
565
+
558
566
  reshaped->members.Append(member);
559
- reshaped->size += AlignLen(member.type->size, stride);
567
+ reshaped->size += (int)AlignLen(member.type->size, stride);
568
+
569
+ member.key = nullptr;
560
570
  }
561
571
  } break;
562
572
 
563
573
  case PrimitiveKind::Array: {
564
574
  reshaped = instance->types.AppendDefault();
565
575
 
576
+ napi_value defn;
577
+ NAPI_OK(napi_get_reference_value(env, type->defn, &defn));
578
+
566
579
  memcpy((void *)reshaped, (const void *)type, K_SIZE(*type));
567
580
  reshaped->ref.stride = stride;
568
581
  reshaped->size = (type->size / type->ref.stride) * stride;
569
- memset((void *)&reshaped->defn, 0, K_SIZE(reshaped->defn));
570
582
  reshaped->flags |= flags;
571
-
572
- Napi::Object defn = type->defn.Value();
573
- reshaped->defn = Napi::Persistent(defn);
583
+ NAPI_OK(napi_create_reference(env, defn, 1, &reshaped->defn));
574
584
  } break;
575
585
 
576
586
  default: { reshaped = (TypeInfo *)type; } break;
@@ -617,7 +627,7 @@ bool CanReturnType(const TypeInfo *type)
617
627
  if (type->countedby)
618
628
  return false;
619
629
 
620
- if (type->primitive == PrimitiveKind::Void && !TestStr(type->name, "void"))
630
+ if (type->primitive == PrimitiveKind::Void && type != type->instance->void_type)
621
631
  return false;
622
632
  if (type->primitive == PrimitiveKind::Array)
623
633
  return false;
@@ -57,7 +57,7 @@ TypeInfo *MakePointerType(InstanceData *instance, const TypeInfo *ref, int count
57
57
  TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size len);
58
58
  TypeInfo *MakeArrayType(InstanceData *instance, const TypeInfo *ref, Size len, ArrayHint hint);
59
59
 
60
- napi_value WrapType(Napi::Env env, const TypeInfo *type, bool freeze = true);
60
+ napi_value WrapType(InstanceData *instance, const TypeInfo *type, bool freeze = true);
61
61
 
62
62
  const TypeInfo *ReshapeType(InstanceData *instance, const TypeInfo *type, int32_t stride, uint16_t flags);
63
63