koffi 2.2.2-beta.1 → 2.2.2-beta.3

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 (45) hide show
  1. package/doc/callbacks.md +8 -0
  2. package/package.json +1 -1
  3. package/src/koffi/build/2.2.2-beta.3/koffi_darwin_arm64.tar.gz +0 -0
  4. package/src/koffi/build/2.2.2-beta.3/koffi_darwin_x64.tar.gz +0 -0
  5. package/src/koffi/build/2.2.2-beta.3/koffi_freebsd_arm64.tar.gz +0 -0
  6. package/src/koffi/build/2.2.2-beta.3/koffi_freebsd_ia32.tar.gz +0 -0
  7. package/src/koffi/build/2.2.2-beta.3/koffi_freebsd_x64.tar.gz +0 -0
  8. package/src/koffi/build/2.2.2-beta.3/koffi_linux_arm32hf.tar.gz +0 -0
  9. package/src/koffi/build/2.2.2-beta.3/koffi_linux_arm64.tar.gz +0 -0
  10. package/src/koffi/build/2.2.2-beta.3/koffi_linux_ia32.tar.gz +0 -0
  11. package/src/koffi/build/2.2.2-beta.3/koffi_linux_riscv64hf64.tar.gz +0 -0
  12. package/src/koffi/build/2.2.2-beta.3/koffi_linux_x64.tar.gz +0 -0
  13. package/src/koffi/build/2.2.2-beta.3/koffi_openbsd_ia32.tar.gz +0 -0
  14. package/src/koffi/build/2.2.2-beta.3/koffi_openbsd_x64.tar.gz +0 -0
  15. package/src/koffi/build/2.2.2-beta.3/koffi_win32_arm64.tar.gz +0 -0
  16. package/src/koffi/build/2.2.2-beta.3/koffi_win32_ia32.tar.gz +0 -0
  17. package/src/koffi/build/2.2.2-beta.3/koffi_win32_x64.tar.gz +0 -0
  18. package/src/koffi/src/abi_arm32.cc +5 -5
  19. package/src/koffi/src/abi_arm64.cc +5 -5
  20. package/src/koffi/src/abi_riscv64.cc +5 -5
  21. package/src/koffi/src/abi_x64_sysv.cc +5 -5
  22. package/src/koffi/src/abi_x64_win.cc +5 -5
  23. package/src/koffi/src/abi_x86.cc +5 -5
  24. package/src/koffi/src/call.cc +35 -18
  25. package/src/koffi/src/call.hh +7 -11
  26. package/src/koffi/src/ffi.cc +82 -35
  27. package/src/koffi/src/ffi.hh +19 -13
  28. package/src/koffi/src/index.js +3 -2
  29. package/src/koffi/test/callbacks.js +11 -0
  30. package/src/koffi/test/misc.c +57 -0
  31. package/src/koffi/build/2.2.2-beta.1/koffi_darwin_arm64.tar.gz +0 -0
  32. package/src/koffi/build/2.2.2-beta.1/koffi_darwin_x64.tar.gz +0 -0
  33. package/src/koffi/build/2.2.2-beta.1/koffi_freebsd_arm64.tar.gz +0 -0
  34. package/src/koffi/build/2.2.2-beta.1/koffi_freebsd_ia32.tar.gz +0 -0
  35. package/src/koffi/build/2.2.2-beta.1/koffi_freebsd_x64.tar.gz +0 -0
  36. package/src/koffi/build/2.2.2-beta.1/koffi_linux_arm32hf.tar.gz +0 -0
  37. package/src/koffi/build/2.2.2-beta.1/koffi_linux_arm64.tar.gz +0 -0
  38. package/src/koffi/build/2.2.2-beta.1/koffi_linux_ia32.tar.gz +0 -0
  39. package/src/koffi/build/2.2.2-beta.1/koffi_linux_riscv64hf64.tar.gz +0 -0
  40. package/src/koffi/build/2.2.2-beta.1/koffi_linux_x64.tar.gz +0 -0
  41. package/src/koffi/build/2.2.2-beta.1/koffi_openbsd_ia32.tar.gz +0 -0
  42. package/src/koffi/build/2.2.2-beta.1/koffi_openbsd_x64.tar.gz +0 -0
  43. package/src/koffi/build/2.2.2-beta.1/koffi_win32_arm64.tar.gz +0 -0
  44. package/src/koffi/build/2.2.2-beta.1/koffi_win32_ia32.tar.gz +0 -0
  45. package/src/koffi/build/2.2.2-beta.1/koffi_win32_x64.tar.gz +0 -0
package/doc/callbacks.md CHANGED
@@ -168,6 +168,14 @@ qsort(koffi.as(array, 'char **'), array.length, koffi.sizeof('void *'), (ptr1, p
168
168
  console.log(array); // Prints ['123', 'bar', 'foo', 'foobar']
169
169
  ```
170
170
 
171
+ ## Asynchronous callbacks
172
+
173
+ *New in Koffi 2.2.2*
174
+
175
+ In Koffi, [asynchronous native calls](functions.md#asynchronous-calls) happen on a secondary thread. However, JS execution is inherently single-threaded, callbacks must run on the main thread.
176
+
177
+ Koffi deals with this by running the JS callback function in the Node.js event loop. This means the callback cannot run while the engine is busy running synchronous code.
178
+
171
179
  ## Handling of exceptions
172
180
 
173
181
  If an exception happens inside the JS callback, the C API will receive 0 or NULL (depending on the return value type).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "2.2.2-beta.1",
3
+ "version": "2.2.2-beta.3",
4
4
  "stable": "2.2.1",
5
5
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
6
6
  "keywords": [
@@ -252,7 +252,7 @@ bool AnalyseFunction(Napi::Env, InstanceData *, FunctionInfo *func)
252
252
  return true;
253
253
  }
254
254
 
255
- bool CallData::Prepare(const Napi::CallbackInfo &info)
255
+ bool CallData::Prepare(const FunctionInfo *func, const Napi::CallbackInfo &info)
256
256
  {
257
257
  uint32_t *args_ptr = nullptr;
258
258
  uint32_t *gpr_ptr = nullptr;
@@ -491,7 +491,7 @@ bool CallData::Prepare(const Napi::CallbackInfo &info)
491
491
  return true;
492
492
  }
493
493
 
494
- void CallData::Execute()
494
+ void CallData::Execute(const FunctionInfo *func)
495
495
  {
496
496
  #define PERFORM_CALL(Suffix) \
497
497
  ([&]() { \
@@ -540,7 +540,7 @@ void CallData::Execute()
540
540
  #undef PERFORM_CALL
541
541
  }
542
542
 
543
- Napi::Value CallData::Complete()
543
+ Napi::Value CallData::Complete(const FunctionInfo *func)
544
544
  {
545
545
  RG_DEFER {
546
546
  PopOutArguments();
@@ -597,12 +597,12 @@ Napi::Value CallData::Complete()
597
597
  RG_UNREACHABLE();
598
598
  }
599
599
 
600
- void CallData::Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegisters *out_reg)
600
+ void CallData::Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, bool async, BackRegisters *out_reg)
601
601
  {
602
602
  if (RG_UNLIKELY(env.IsExceptionPending()))
603
603
  return;
604
604
 
605
- const TrampolineInfo &trampoline = instance->trampolines[idx];
605
+ const TrampolineInfo &trampoline = shared.trampolines[idx];
606
606
 
607
607
  const FunctionInfo *proto = trampoline.proto;
608
608
  Napi::Function func = trampoline.func.Value();
@@ -267,7 +267,7 @@ bool AnalyseFunction(Napi::Env, InstanceData *, FunctionInfo *func)
267
267
  return true;
268
268
  }
269
269
 
270
- bool CallData::Prepare(const Napi::CallbackInfo &info)
270
+ bool CallData::Prepare(const FunctionInfo *func, const Napi::CallbackInfo &info)
271
271
  {
272
272
  uint64_t *args_ptr = nullptr;
273
273
  uint64_t *gpr_ptr = nullptr;
@@ -559,7 +559,7 @@ bool CallData::Prepare(const Napi::CallbackInfo &info)
559
559
  return true;
560
560
  }
561
561
 
562
- void CallData::Execute()
562
+ void CallData::Execute(const FunctionInfo *func)
563
563
  {
564
564
  #define PERFORM_CALL(Suffix) \
565
565
  ([&]() { \
@@ -611,7 +611,7 @@ void CallData::Execute()
611
611
  #undef PERFORM_CALL
612
612
  }
613
613
 
614
- Napi::Value CallData::Complete()
614
+ Napi::Value CallData::Complete(const FunctionInfo *func)
615
615
  {
616
616
  RG_DEFER {
617
617
  PopOutArguments();
@@ -673,12 +673,12 @@ Napi::Value CallData::Complete()
673
673
  RG_UNREACHABLE();
674
674
  }
675
675
 
676
- void CallData::Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegisters *out_reg)
676
+ void CallData::Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, bool async, BackRegisters *out_reg)
677
677
  {
678
678
  if (RG_UNLIKELY(env.IsExceptionPending()))
679
679
  return;
680
680
 
681
- const TrampolineInfo &trampoline = instance->trampolines[idx];
681
+ const TrampolineInfo &trampoline = shared.trampolines[idx];
682
682
 
683
683
  const FunctionInfo *proto = trampoline.proto;
684
684
  Napi::Function func = trampoline.func.Value();
@@ -196,7 +196,7 @@ bool AnalyseFunction(Napi::Env, InstanceData *, FunctionInfo *func)
196
196
  return true;
197
197
  }
198
198
 
199
- bool CallData::Prepare(const Napi::CallbackInfo &info)
199
+ bool CallData::Prepare(const FunctionInfo *func, const Napi::CallbackInfo &info)
200
200
  {
201
201
  uint64_t *args_ptr = nullptr;
202
202
  uint64_t *gpr_ptr = nullptr;
@@ -406,7 +406,7 @@ bool CallData::Prepare(const Napi::CallbackInfo &info)
406
406
  return true;
407
407
  }
408
408
 
409
- void CallData::Execute()
409
+ void CallData::Execute(const FunctionInfo *func)
410
410
  {
411
411
  #define PERFORM_CALL(Suffix) \
412
412
  ([&]() { \
@@ -462,7 +462,7 @@ void CallData::Execute()
462
462
  #undef PERFORM_CALL
463
463
  }
464
464
 
465
- Napi::Value CallData::Complete()
465
+ Napi::Value CallData::Complete(const FunctionInfo *func)
466
466
  {
467
467
  RG_DEFER {
468
468
  PopOutArguments();
@@ -524,12 +524,12 @@ Napi::Value CallData::Complete()
524
524
  RG_UNREACHABLE();
525
525
  }
526
526
 
527
- void CallData::Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegisters *out_reg)
527
+ void CallData::Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, bool async, BackRegisters *out_reg)
528
528
  {
529
529
  if (RG_UNLIKELY(env.IsExceptionPending()))
530
530
  return;
531
531
 
532
- const TrampolineInfo &trampoline = instance->trampolines[idx];
532
+ const TrampolineInfo &trampoline = shared.trampolines[idx];
533
533
 
534
534
  const FunctionInfo *proto = trampoline.proto;
535
535
  Napi::Function func = trampoline.func.Value();
@@ -284,7 +284,7 @@ bool AnalyseFunction(Napi::Env, InstanceData *, FunctionInfo *func)
284
284
  return true;
285
285
  }
286
286
 
287
- bool CallData::Prepare(const Napi::CallbackInfo &info)
287
+ bool CallData::Prepare(const FunctionInfo *func, const Napi::CallbackInfo &info)
288
288
  {
289
289
  uint64_t *args_ptr = nullptr;
290
290
  uint64_t *gpr_ptr = nullptr;
@@ -470,7 +470,7 @@ bool CallData::Prepare(const Napi::CallbackInfo &info)
470
470
  return true;
471
471
  }
472
472
 
473
- void CallData::Execute()
473
+ void CallData::Execute(const FunctionInfo *func)
474
474
  {
475
475
  #define PERFORM_CALL(Suffix) \
476
476
  ([&]() { \
@@ -526,7 +526,7 @@ void CallData::Execute()
526
526
  #undef PERFORM_CALL
527
527
  }
528
528
 
529
- Napi::Value CallData::Complete()
529
+ Napi::Value CallData::Complete(const FunctionInfo *func)
530
530
  {
531
531
  RG_DEFER {
532
532
  PopOutArguments();
@@ -583,12 +583,12 @@ Napi::Value CallData::Complete()
583
583
  RG_UNREACHABLE();
584
584
  }
585
585
 
586
- void CallData::Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegisters *out_reg)
586
+ void CallData::Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, bool async, BackRegisters *out_reg)
587
587
  {
588
588
  if (RG_UNLIKELY(env.IsExceptionPending()))
589
589
  return;
590
590
 
591
- const TrampolineInfo &trampoline = instance->trampolines[idx];
591
+ const TrampolineInfo &trampoline = shared.trampolines[idx];
592
592
 
593
593
  const FunctionInfo *proto = trampoline.proto;
594
594
  Napi::Function func = trampoline.func.Value();
@@ -121,7 +121,7 @@ bool AnalyseFunction(Napi::Env, InstanceData *, FunctionInfo *func)
121
121
  return true;
122
122
  }
123
123
 
124
- bool CallData::Prepare(const Napi::CallbackInfo &info)
124
+ bool CallData::Prepare(const FunctionInfo *func, const Napi::CallbackInfo &info)
125
125
  {
126
126
  uint64_t *args_ptr = nullptr;
127
127
 
@@ -281,7 +281,7 @@ bool CallData::Prepare(const Napi::CallbackInfo &info)
281
281
  return true;
282
282
  }
283
283
 
284
- void CallData::Execute()
284
+ void CallData::Execute(const FunctionInfo *func)
285
285
  {
286
286
  #define PERFORM_CALL(Suffix) \
287
287
  ([&]() { \
@@ -322,7 +322,7 @@ void CallData::Execute()
322
322
  #undef PERFORM_CALL
323
323
  }
324
324
 
325
- Napi::Value CallData::Complete()
325
+ Napi::Value CallData::Complete(const FunctionInfo *func)
326
326
  {
327
327
  RG_DEFER {
328
328
  PopOutArguments();
@@ -379,12 +379,12 @@ Napi::Value CallData::Complete()
379
379
  RG_UNREACHABLE();
380
380
  }
381
381
 
382
- void CallData::Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegisters *out_reg)
382
+ void CallData::Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, bool async, BackRegisters *out_reg)
383
383
  {
384
384
  if (RG_UNLIKELY(env.IsExceptionPending()))
385
385
  return;
386
386
 
387
- const TrampolineInfo &trampoline = instance->trampolines[idx];
387
+ const TrampolineInfo &trampoline = shared.trampolines[idx];
388
388
 
389
389
  const FunctionInfo *proto = trampoline.proto;
390
390
  Napi::Function func = trampoline.func.Value();
@@ -172,7 +172,7 @@ bool AnalyseFunction(Napi::Env env, InstanceData *instance, FunctionInfo *func)
172
172
  return true;
173
173
  }
174
174
 
175
- bool CallData::Prepare(const Napi::CallbackInfo &info)
175
+ bool CallData::Prepare(const FunctionInfo *func, const Napi::CallbackInfo &info)
176
176
  {
177
177
  uint32_t *args_ptr = nullptr;
178
178
  uint32_t *fast_ptr = nullptr;
@@ -362,7 +362,7 @@ bool CallData::Prepare(const Napi::CallbackInfo &info)
362
362
  return true;
363
363
  }
364
364
 
365
- void CallData::Execute()
365
+ void CallData::Execute(const FunctionInfo *func)
366
366
  {
367
367
  #define PERFORM_CALL(Suffix) \
368
368
  ([&]() { \
@@ -404,7 +404,7 @@ void CallData::Execute()
404
404
  #undef PERFORM_CALL
405
405
  }
406
406
 
407
- Napi::Value CallData::Complete()
407
+ Napi::Value CallData::Complete(const FunctionInfo *func)
408
408
  {
409
409
  RG_DEFER {
410
410
  PopOutArguments();
@@ -461,12 +461,12 @@ Napi::Value CallData::Complete()
461
461
  RG_UNREACHABLE();
462
462
  }
463
463
 
464
- void CallData::Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegisters *out_reg)
464
+ void CallData::Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, bool async, BackRegisters *out_reg)
465
465
  {
466
466
  if (RG_UNLIKELY(env.IsExceptionPending()))
467
467
  return;
468
468
 
469
- const TrampolineInfo &trampoline = instance->trampolines[idx];
469
+ const TrampolineInfo &trampoline = shared.trampolines[idx];
470
470
 
471
471
  const FunctionInfo *proto = trampoline.proto;
472
472
  Napi::Function func = trampoline.func.Value();
@@ -33,10 +33,9 @@ struct RelayContext {
33
33
  bool done = false;
34
34
  };
35
35
 
36
- CallData::CallData(Napi::Env env, InstanceData *instance,
37
- const FunctionInfo *func, InstanceMemory *mem, bool async)
38
- : env(env), instance(instance), func(func),
39
- mem(mem), old_stack_mem(mem->stack), old_heap_mem(mem->heap), async(async)
36
+ CallData::CallData(Napi::Env env, InstanceData *instance, InstanceMemory *mem)
37
+ : env(env), instance(instance),
38
+ mem(mem), old_stack_mem(mem->stack), old_heap_mem(mem->heap)
40
39
  {
41
40
  mem->generation += !mem->depth;
42
41
  mem->depth++;
@@ -54,7 +53,21 @@ CallData::~CallData()
54
53
  mem->stack = old_stack_mem;
55
54
  mem->heap = old_heap_mem;
56
55
 
57
- instance->temp_trampolines -= used_trampolines;
56
+ if (used_trampolines) {
57
+ std::lock_guard<std::mutex> lock(shared.mutex);
58
+
59
+ for (Size i = 0; i < MaxTrampolines; i++) {
60
+ if (used_trampolines & (1u << i)) {
61
+ TrampolineInfo *trampoline = &shared.trampolines[i];
62
+
63
+ trampoline->func.Reset();
64
+ trampoline->recv.Reset();
65
+ }
66
+ }
67
+
68
+ shared.temp_trampolines ^= used_trampolines;
69
+ }
70
+
58
71
  instance->temporaries -= mem->temporary;
59
72
 
60
73
  if (!--mem->depth && mem->temporary) {
@@ -66,7 +79,7 @@ CallData::~CallData()
66
79
 
67
80
  void CallData::RelaySafe(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegisters *out_reg)
68
81
  {
69
- if (async) {
82
+ if (std::this_thread::get_id() != instance->main_thread_id) {
70
83
  RelayContext ctx;
71
84
 
72
85
  ctx.call = this;
@@ -83,7 +96,7 @@ void CallData::RelaySafe(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegi
83
96
  ctx.cv.wait(lock);
84
97
  }
85
98
  } else {
86
- Relay(idx, own_sp, caller_sp, out_reg);
99
+ Relay(idx, own_sp, caller_sp, false, out_reg);
87
100
  }
88
101
  }
89
102
 
@@ -91,7 +104,7 @@ void CallData::RelayAsync(napi_env, napi_value, void *, void *udata)
91
104
  {
92
105
  RelayContext *ctx = (RelayContext *)udata;
93
106
 
94
- ctx->call->Relay(ctx->idx, ctx->own_sp, ctx->caller_sp, ctx->out_reg);
107
+ ctx->call->Relay(ctx->idx, ctx->own_sp, ctx->caller_sp, true, ctx->out_reg);
95
108
 
96
109
  // We're done!
97
110
  std::lock_guard<std::mutex> lock(ctx->mutex);
@@ -1012,18 +1025,22 @@ void CallData::PopOutArguments()
1012
1025
 
1013
1026
  void *CallData::ReserveTrampoline(const FunctionInfo *proto, Napi::Function func)
1014
1027
  {
1015
- if (RG_UNLIKELY(instance->temp_trampolines >= MaxTrampolines)) {
1016
- ThrowError<Napi::Error>(env, "Too many temporary callbacks are in use (max = %1)", MaxTrampolines);
1017
- return nullptr;
1018
- }
1028
+ int idx;
1029
+ {
1030
+ std::lock_guard<std::mutex> lock(shared.mutex);
1019
1031
 
1020
- int idx = instance->next_trampoline;
1032
+ idx = CountTrailingZeros(~shared.temp_trampolines);
1021
1033
 
1022
- instance->next_trampoline = (int16_t)((instance->next_trampoline + 1) % MaxTrampolines);
1023
- instance->temp_trampolines++;
1024
- used_trampolines++;
1034
+ if (RG_UNLIKELY(idx >= MaxTrampolines)) {
1035
+ ThrowError<Napi::Error>(env, "Too many temporary callbacks are in use (max = %1)", MaxTrampolines);
1036
+ return env.Null();
1037
+ }
1038
+
1039
+ shared.temp_trampolines |= 1u << idx;
1040
+ used_trampolines |= 1u << idx;
1041
+ }
1025
1042
 
1026
- TrampolineInfo *trampoline = &instance->trampolines[idx];
1043
+ TrampolineInfo *trampoline = &shared.trampolines[idx];
1027
1044
 
1028
1045
  trampoline->proto = proto;
1029
1046
  trampoline->func.Reset(func, 1);
@@ -1034,7 +1051,7 @@ void *CallData::ReserveTrampoline(const FunctionInfo *proto, Napi::Function func
1034
1051
  return ptr;
1035
1052
  }
1036
1053
 
1037
- void CallData::DumpForward() const
1054
+ void CallData::DumpForward(const FunctionInfo *func) const
1038
1055
  {
1039
1056
  PrintLn(stderr, "%!..+---- %1 (%2) ----%!0", func->name, CallConventionNames[(int)func->convention]);
1040
1057
 
@@ -36,15 +36,12 @@ class alignas(8) CallData {
36
36
 
37
37
  Napi::Env env;
38
38
  InstanceData *instance;
39
- const FunctionInfo *func;
40
39
 
41
40
  InstanceMemory *mem;
42
41
  Span<uint8_t> old_stack_mem;
43
42
  Span<uint8_t> old_heap_mem;
44
43
 
45
- bool async;
46
-
47
- int16_t used_trampolines = 0;
44
+ uint32_t used_trampolines = 0;
48
45
 
49
46
  LocalArray<OutArgument, MaxOutParameters> out_arguments;
50
47
 
@@ -70,8 +67,7 @@ class alignas(8) CallData {
70
67
  BlockAllocator call_alloc;
71
68
 
72
69
  public:
73
- CallData(Napi::Env env, InstanceData *instance,
74
- const FunctionInfo *func, InstanceMemory *mem, bool async);
70
+ CallData(Napi::Env env, InstanceData *instance, InstanceMemory *mem);
75
71
  ~CallData();
76
72
 
77
73
  #ifdef UNITY_BUILD
@@ -84,17 +80,17 @@ public:
84
80
  #define INLINE_IF_UNITY
85
81
  #endif
86
82
 
87
- INLINE_IF_UNITY bool Prepare(const Napi::CallbackInfo &info);
88
- INLINE_IF_UNITY void Execute();
89
- INLINE_IF_UNITY Napi::Value Complete();
83
+ INLINE_IF_UNITY bool Prepare(const FunctionInfo *func, const Napi::CallbackInfo &info);
84
+ INLINE_IF_UNITY void Execute(const FunctionInfo *func);
85
+ INLINE_IF_UNITY Napi::Value Complete(const FunctionInfo *func);
90
86
 
91
87
  #undef INLINE_IF_UNITY
92
88
 
93
- void Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegisters *out_reg);
89
+ void Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, bool async, BackRegisters *out_reg);
94
90
  void RelaySafe(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegisters *out_reg);
95
91
  static void RelayAsync(napi_env, napi_value, void *, void *udata);
96
92
 
97
- void DumpForward() const;
93
+ void DumpForward(const FunctionInfo *func) const;
98
94
 
99
95
  private:
100
96
  template <typename T>
@@ -40,6 +40,8 @@
40
40
 
41
41
  namespace RG {
42
42
 
43
+ SharedData shared;
44
+
43
45
  // Value does not matter, the tag system uses memory addresses
44
46
  const int TypeInfoMarker = 0xDEADBEEF;
45
47
  const int CastMarker = 0xDEADBEEF;
@@ -1059,16 +1061,16 @@ static Napi::Value TranslateNormalCall(const Napi::CallbackInfo &info)
1059
1061
  }
1060
1062
 
1061
1063
  InstanceMemory *mem = instance->memories[0];
1062
- CallData call(env, instance, func, mem, false);
1064
+ CallData call(env, instance, mem);
1063
1065
 
1064
1066
  RG_DEFER_C(prev_call = exec_call) { exec_call = prev_call; };
1065
1067
  exec_call = &call;
1066
1068
 
1067
- if (!RG_UNLIKELY(call.Prepare(info)))
1069
+ if (!RG_UNLIKELY(call.Prepare(func, info)))
1068
1070
  return env.Null();
1069
1071
 
1070
1072
  if (instance->debug) {
1071
- call.DumpForward();
1073
+ call.DumpForward(func);
1072
1074
  }
1073
1075
 
1074
1076
  // Execute call
@@ -1076,10 +1078,10 @@ static Napi::Value TranslateNormalCall(const Napi::CallbackInfo &info)
1076
1078
  RG_DEFER_C(prev_call = exec_call) { exec_call = prev_call; };
1077
1079
  exec_call = &call;
1078
1080
 
1079
- call.Execute();
1081
+ call.Execute(func);
1080
1082
  }
1081
1083
 
1082
- return call.Complete();
1084
+ return call.Complete(func);
1083
1085
  }
1084
1086
 
1085
1087
  static Napi::Value TranslateVariadicCall(const Napi::CallbackInfo &info)
@@ -1136,13 +1138,13 @@ static Napi::Value TranslateVariadicCall(const Napi::CallbackInfo &info)
1136
1138
  return env.Null();
1137
1139
 
1138
1140
  InstanceMemory *mem = instance->memories[0];
1139
- CallData call(env, instance, &func, mem, false);
1141
+ CallData call(env, instance, mem);
1140
1142
 
1141
- if (!RG_UNLIKELY(call.Prepare(info)))
1143
+ if (!RG_UNLIKELY(call.Prepare(&func, info)))
1142
1144
  return env.Null();
1143
1145
 
1144
1146
  if (instance->debug) {
1145
- call.DumpForward();
1147
+ call.DumpForward(&func);
1146
1148
  }
1147
1149
 
1148
1150
  // Execute call
@@ -1150,10 +1152,10 @@ static Napi::Value TranslateVariadicCall(const Napi::CallbackInfo &info)
1150
1152
  RG_DEFER_C(prev_call = exec_call) { exec_call = prev_call; };
1151
1153
  exec_call = &call;
1152
1154
 
1153
- call.Execute();
1155
+ call.Execute(&func);
1154
1156
  }
1155
1157
 
1156
- return call.Complete();
1158
+ return call.Complete(&func);
1157
1159
  }
1158
1160
 
1159
1161
  class AsyncCall: public Napi::AsyncWorker {
@@ -1167,11 +1169,11 @@ public:
1167
1169
  AsyncCall(Napi::Env env, InstanceData *instance, const FunctionInfo *func,
1168
1170
  InstanceMemory *mem, Napi::Function &callback)
1169
1171
  : Napi::AsyncWorker(callback), env(env), func(func->Ref()),
1170
- call(env, instance, func, mem, true) {}
1172
+ call(env, instance, mem) {}
1171
1173
  ~AsyncCall() { func->Unref(); }
1172
1174
 
1173
1175
  bool Prepare(const Napi::CallbackInfo &info) {
1174
- prepared = call.Prepare(info);
1176
+ prepared = call.Prepare(func, info);
1175
1177
 
1176
1178
  if (!prepared) {
1177
1179
  Napi::Error err = env.GetAndClearPendingException();
@@ -1180,7 +1182,7 @@ public:
1180
1182
 
1181
1183
  return prepared;
1182
1184
  }
1183
- void DumpForward() { call.DumpForward(); }
1185
+ void DumpForward() { call.DumpForward(func); }
1184
1186
 
1185
1187
  void Execute() override;
1186
1188
  void OnOK() override;
@@ -1192,7 +1194,7 @@ void AsyncCall::Execute()
1192
1194
  RG_DEFER_C(prev_call = exec_call) { exec_call = prev_call; };
1193
1195
  exec_call = &call;
1194
1196
 
1195
- call.Execute();
1197
+ call.Execute(func);
1196
1198
  }
1197
1199
  }
1198
1200
 
@@ -1205,7 +1207,7 @@ void AsyncCall::OnOK()
1205
1207
  Napi::Value self = env.Null();
1206
1208
  napi_value args[] = {
1207
1209
  env.Null(),
1208
- call.Complete()
1210
+ call.Complete(func)
1209
1211
  };
1210
1212
 
1211
1213
  callback.Call(self, RG_LEN(args), args);
@@ -1434,18 +1436,22 @@ static Napi::Value RegisterCallback(const Napi::CallbackInfo &info)
1434
1436
  return env.Null();
1435
1437
  }
1436
1438
 
1439
+ int idx;
1440
+ {
1441
+ std::lock_guard<std::mutex> lock(shared.mutex);
1437
1442
 
1438
- int idx = CountTrailingZeros(~instance->registered_trampolines);
1443
+ idx = CountTrailingZeros(~shared.registered_trampolines);
1439
1444
 
1440
- if (RG_UNLIKELY(idx >= MaxTrampolines)) {
1441
- ThrowError<Napi::Error>(env, "Too many registered callbacks are in use (max = %1)", MaxTrampolines);
1442
- return env.Null();
1443
- }
1445
+ if (RG_UNLIKELY(idx >= MaxTrampolines)) {
1446
+ ThrowError<Napi::Error>(env, "Too many registered callbacks are in use (max = %1)", MaxTrampolines);
1447
+ return env.Null();
1448
+ }
1444
1449
 
1445
- instance->registered_trampolines |= 1u << idx;
1446
- idx += MaxTrampolines;
1450
+ shared.registered_trampolines |= 1u << idx;
1451
+ idx += MaxTrampolines;
1452
+ }
1447
1453
 
1448
- TrampolineInfo *trampoline = &instance->trampolines[idx];
1454
+ TrampolineInfo *trampoline = &shared.trampolines[idx];
1449
1455
 
1450
1456
  trampoline->proto = type->ref.proto;
1451
1457
  trampoline->func.Reset(func, 1);
@@ -1481,19 +1487,26 @@ static Napi::Value UnregisterCallback(const Napi::CallbackInfo &info)
1481
1487
  Napi::External<void> external = info[0].As<Napi::External<void>>();
1482
1488
  void *ptr = external.Data();
1483
1489
 
1484
- for (Size i = 0; i < MaxTrampolines; i++) {
1485
- Size idx = i + MaxTrampolines;
1490
+ // Release shared trampoline safely
1491
+ {
1492
+ std::lock_guard<std::mutex> lock(shared.mutex);
1493
+
1494
+ for (Size i = 0; i < MaxTrampolines; i++) {
1495
+ Size idx = i + MaxTrampolines;
1496
+
1497
+ if (!(shared.registered_trampolines & (1u << i)))
1498
+ continue;
1486
1499
 
1487
- if (!(instance->registered_trampolines & (1u << i)))
1488
- continue;
1500
+ TrampolineInfo *trampoline = &shared.trampolines[idx];
1489
1501
 
1490
- TrampolineInfo *trampoline = &instance->trampolines[idx];
1502
+ if (GetTrampoline(idx, trampoline->proto) == ptr) {
1503
+ shared.registered_trampolines &= ~(1u << i);
1491
1504
 
1492
- if (GetTrampoline(idx, trampoline->proto) == ptr) {
1493
- instance->registered_trampolines &= ~(1u << i);
1494
- trampoline->recv.Reset();
1505
+ trampoline->func.Reset();
1506
+ trampoline->recv.Reset();
1495
1507
 
1496
- return env.Undefined();
1508
+ return env.Undefined();
1509
+ }
1497
1510
  }
1498
1511
  }
1499
1512
 
@@ -1696,6 +1709,25 @@ InstanceData::~InstanceData()
1696
1709
  delete mem;
1697
1710
  }
1698
1711
 
1712
+ // Clean-up leftover registered trampolines
1713
+ {
1714
+ std::lock_guard<std::mutex> lock(shared.mutex);
1715
+
1716
+ for (Size i = 0; i < MaxTrampolines; i++) {
1717
+ Size idx = i + MaxTrampolines;
1718
+
1719
+ if (!(shared.registered_trampolines & (1u << i)))
1720
+ continue;
1721
+
1722
+ TrampolineInfo *trampoline = &shared.trampolines[idx];
1723
+
1724
+ if (trampoline->func.Env().GetInstanceData<InstanceData>() == this) {
1725
+ trampoline->func.Reset();
1726
+ trampoline->recv.Reset();
1727
+ }
1728
+ }
1729
+ }
1730
+
1699
1731
  if (broker) {
1700
1732
  napi_release_threadsafe_function(broker, napi_tsfn_abort);
1701
1733
  }
@@ -1856,7 +1888,21 @@ static Napi::Value DecodeValue(const Napi::CallbackInfo &info)
1856
1888
 
1857
1889
  extern "C" void RelayCallback(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegisters *out_reg)
1858
1890
  {
1859
- exec_call->RelaySafe(idx, own_sp, caller_sp, out_reg);
1891
+ if (RG_LIKELY(exec_call)) {
1892
+ exec_call->RelaySafe(idx, own_sp, caller_sp, out_reg);
1893
+ } else {
1894
+ Napi::Env env = shared.trampolines[idx].func.Env();
1895
+ InstanceData *instance = env.GetInstanceData<InstanceData>();
1896
+
1897
+ InstanceMemory *mem = AllocateMemory(instance, instance->async_stack_size, instance->async_heap_size);
1898
+ if (RG_UNLIKELY(!mem)) {
1899
+ ThrowError<Napi::Error>(env, "Too many asynchronous calls are running");
1900
+ return;
1901
+ }
1902
+
1903
+ CallData call(env, instance, mem);
1904
+ call.RelaySafe(idx, own_sp, caller_sp, out_reg);
1905
+ }
1860
1906
  }
1861
1907
 
1862
1908
  static InstanceData *CreateInstance(Napi::Env env)
@@ -1864,9 +1910,10 @@ static InstanceData *CreateInstance(Napi::Env env)
1864
1910
  InstanceData *instance = new InstanceData();
1865
1911
  RG_DEFER_N(err_guard) { delete instance; };
1866
1912
 
1867
- Napi::String resource_name = Napi::String::New(env, "Koffi Async Callback Broker");
1913
+ instance->main_thread_id = std::this_thread::get_id();
1868
1914
 
1869
- if (napi_create_threadsafe_function(env, nullptr, nullptr, resource_name,
1915
+ if (napi_create_threadsafe_function(env, nullptr, nullptr,
1916
+ Napi::String::New(env, "Koffi Async Callback Broker"),
1870
1917
  0, 1, nullptr, nullptr, nullptr,
1871
1918
  CallData::RelayAsync, &instance->broker) != napi_ok) {
1872
1919
  LogError("Failed to create async callback broker");
@@ -233,14 +233,6 @@ struct InstanceMemory {
233
233
  bool temporary;
234
234
  };
235
235
 
236
- struct TrampolineInfo {
237
- const FunctionInfo *proto;
238
- Napi::FunctionReference func;
239
- Napi::Reference<Napi::Value> recv;
240
-
241
- int32_t generation;
242
- };
243
-
244
236
  struct InstanceData {
245
237
  ~InstanceData();
246
238
 
@@ -258,11 +250,7 @@ struct InstanceData {
258
250
  LocalArray<InstanceMemory *, 9> memories;
259
251
  int temporaries = 0;
260
252
 
261
- TrampolineInfo trampolines[MaxTrampolines * 2];
262
- int16_t next_trampoline = 0;
263
- int16_t temp_trampolines = 0;
264
- uint32_t registered_trampolines = 0;
265
-
253
+ std::thread::id main_thread_id;
266
254
  napi_threadsafe_function broker = nullptr;
267
255
 
268
256
  BlockAllocator str_alloc;
@@ -280,4 +268,22 @@ RG_STATIC_ASSERT(DefaultMaxAsyncCalls >= DefaultResidentAsyncPools);
280
268
  RG_STATIC_ASSERT(MaxAsyncCalls >= DefaultMaxAsyncCalls);
281
269
  RG_STATIC_ASSERT(MaxTrampolines <= 16);
282
270
 
271
+ struct TrampolineInfo {
272
+ const FunctionInfo *proto;
273
+ Napi::FunctionReference func;
274
+ Napi::Reference<Napi::Value> recv;
275
+
276
+ int32_t generation;
277
+ };
278
+
279
+ struct SharedData {
280
+ std::mutex mutex;
281
+
282
+ TrampolineInfo trampolines[MaxTrampolines * 2];
283
+ uint32_t temp_trampolines = 0;
284
+ uint32_t registered_trampolines = 0;
285
+ };
286
+
287
+ extern SharedData shared;
288
+
283
289
  }
@@ -18,10 +18,11 @@
18
18
  const util = require('util');
19
19
 
20
20
  let filename = __dirname + '/../build/koffi.node';
21
+ let native = require(filename);
21
22
 
22
23
  module.exports = {
23
- ...require(filename),
24
+ ...native,
24
25
 
25
26
  // Deprecated functions
26
- handle: util.deprecate(module.exports.opaque, 'The koffi.handle() function was deprecated in Koffi 2.1, use koffi.opaque() instead', 'KOFFI001');
27
+ handle: util.deprecate(native.opaque, 'The koffi.handle() function was deprecated in Koffi 2.1, use koffi.opaque() instead', 'KOFFI001')
27
28
  };
@@ -75,6 +75,7 @@ async function test() {
75
75
  const ApplyStruct = lib.func('int ApplyStruct(int x, StructCallbacks callbacks)');
76
76
  const SetCallback = lib.func('void SetCallback(IntCallback *func)');
77
77
  const CallCallback = lib.func('int CallCallback(int x)');
78
+ const CallFromThread = lib.func('int CallFromThread(int x)');
78
79
  const MakeVectors = lib.func('int MakeVectors(int len, VectorCallback *func)');
79
80
  const CallQSort = lib.func('void CallQSort(_Inout_ void *base, size_t nmemb, size_t size, SortCallback *cb)');
80
81
  const CallMeChar = lib.func('int CallMeChar(CharCallback *func)');
@@ -260,4 +261,14 @@ async function test() {
260
261
 
261
262
  assert.equal(ret, 97 + 2 * 98);
262
263
  }
264
+
265
+ // Use callback from secondary thread
266
+ for (let i = 0; i < 128; i++) {
267
+ let cb = koffi.register(x => -x - 2, koffi.pointer(IntCallback));
268
+
269
+ SetCallback(cb);
270
+ assert.equal(await util.promisify(CallFromThread.async)(27), -29);
271
+
272
+ koffi.unregister(cb);
273
+ }
263
274
  }
@@ -22,6 +22,14 @@
22
22
  typedef uint16_t char16_t;
23
23
  typedef uint32_t char32_t;
24
24
  #endif
25
+ #ifdef _WIN32
26
+ #define NOMINMAX
27
+ #define WIN32_LEAN_AND_MEAN
28
+ #include <windows.h>
29
+ #else
30
+ #include <errno.h>
31
+ #include <pthread.h>
32
+ #endif
25
33
 
26
34
  #ifdef _WIN32
27
35
  #define EXPORT __declspec(dllexport)
@@ -671,6 +679,55 @@ EXPORT int CallCallback(int x)
671
679
  return callback(x);
672
680
  }
673
681
 
682
+ #ifdef _WIN32
683
+
684
+ static DWORD WINAPI CallFromThreadFunc(void *udata)
685
+ {
686
+ int *ptr = (int *)udata;
687
+ *ptr = callback(*ptr);
688
+
689
+ return 0;
690
+ }
691
+
692
+ EXPORT int CallFromThread(int x)
693
+ {
694
+ HANDLE h = CreateThread(NULL, 0, CallFromThreadFunc, &x, 0, NULL);
695
+ if (!h) {
696
+ perror("CreateThread");
697
+ exit(1);
698
+ }
699
+
700
+ WaitForSingleObject(h, INFINITE);
701
+
702
+ return x;
703
+ }
704
+
705
+ #else
706
+
707
+ static void *CallFromThreadFunc(void *udata)
708
+ {
709
+ int *ptr = (int *)udata;
710
+ *ptr = callback(*ptr);
711
+
712
+ return NULL;
713
+ }
714
+
715
+ EXPORT int CallFromThread(int x)
716
+ {
717
+ pthread_t thread;
718
+
719
+ if (pthread_create(&thread, NULL, CallFromThreadFunc, &x)) {
720
+ perror("pthread_create");
721
+ exit(1);
722
+ }
723
+
724
+ pthread_join(thread, NULL);
725
+
726
+ return x;
727
+ }
728
+
729
+ #endif
730
+
674
731
  EXPORT void ReverseBytes(void *p, int len)
675
732
  {
676
733
  uint8_t *bytes = (uint8_t *)p;