koffi 0.9.33 → 0.9.36

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.
package/CMakeLists.txt CHANGED
@@ -30,6 +30,7 @@ set(KOFFI_SRC
30
30
  src/call_x64_win.cc
31
31
  src/call_x86.cc
32
32
  src/ffi.cc
33
+ src/parser.cc
33
34
  src/util.cc
34
35
  vendor/libcc/libcc.cc
35
36
  )
@@ -54,7 +55,7 @@ endif()
54
55
  add_node_addon(NAME koffi SOURCES ${KOFFI_SRC})
55
56
  target_include_directories(koffi PRIVATE . vendor/node-addon-api)
56
57
 
57
- target_compile_definitions(koffi PRIVATE FELIX_TARGET=koffi NAPI_DISABLE_CPP_EXCEPTIONS)
58
+ target_compile_definitions(koffi PRIVATE FELIX_TARGET=koffi NAPI_DISABLE_CPP_EXCEPTIONS NAPI_VERSION=8)
58
59
  if(WIN32)
59
60
  target_compile_definitions(koffi PRIVATE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE)
60
61
  target_link_libraries(koffi PRIVATE ws2_32)
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # Table of contents
2
2
 
3
3
  - [Introduction](#introduction)
4
+ - [Benchmarks](#benchmarks)
5
+ * [atoi results](#atoi-results)
6
+ * [Raylib results](#raylib-results)
4
7
  - [Installation](#installation)
5
8
  * [Windows](#windows)
6
9
  * [Other platforms](#other-platforms)
7
10
  - [Get started](#get-started)
8
11
  - [Tests](#tests)
9
- - [Benchmarks](#benchmarks)
10
- * [atoi results](#atoi-results)
11
- * [Raylib results](#raylib-results)
12
12
 
13
13
  # Introduction
14
14
 
@@ -27,12 +27,13 @@ The following platforms __are officially supported and tested__ at the moment:
27
27
  * FreeBSD ARM64 Little Endian
28
28
  * macOS x86_64
29
29
 
30
- The following platforms will __soon be officially supported__:
30
+ The following platforms will __soon be officially supported__, when I can get my hand on a machine...:
31
31
 
32
32
  * macOS ARM64
33
33
 
34
34
  The following platforms __may be supported__ but are not tested:
35
35
 
36
+ * Linux ARM32 (full software FP) Little Endian
36
37
  * NetBSD x86_64
37
38
  * NetBSD ARM64
38
39
  * OpenBSD x86_64
@@ -40,6 +41,65 @@ The following platforms __may be supported__ but are not tested:
40
41
 
41
42
  This is still in development, bugs are to expected. More tests will come in the near future.
42
43
 
44
+ # Benchmarks
45
+
46
+ In order to run it, go to `koffi/benchmark` and run `../../cnoke/cnoke.js` (or `node ..\..\cnoke\cnoke.js` on Windows) before doing anything else.
47
+
48
+ Once this is done, you can execute each implementation, e.g. `build/atoi_cc` or `./atoi_koffi.js`. You can optionally define a custom number of iterations, e.g. `./atoi_koffi.js 10000000`.
49
+
50
+ ## atoi results
51
+
52
+ This test is based around repeated calls to a simple standard C function atoi, and has three implementations:
53
+ - the first one is the reference, it calls atoi through an N-API module, and is close to the theoretical limit of a perfect (no overhead) Node.js > C FFI implementation.
54
+ - the second one calls atoi through Koffi
55
+ - the third one uses the official Node.js FFI implementation, node-ffi-napi
56
+
57
+ Because atoi is a small call, the FFI overhead is clearly visible.
58
+
59
+ ### Linux
60
+
61
+ The results below were measured on my x86_64 Linux machine (AMD® Ryzen™ 7 5800H 16G):
62
+
63
+ Benchmark | Iterations | Total time | Overhead
64
+ ------------- | ---------- | ----------- | ----------
65
+ atoi_napi | 20000000 | 1.10s | (baseline)
66
+ atoi_koffi | 20000000 | 1.91s | x1.73
67
+ atoi_node_ffi | 20000000 | 640.49s | x582
68
+
69
+ ### Windows
70
+
71
+ The results below were measured on my x86_64 Windows machine (AMD® Ryzen™ 7 5800H 16G):
72
+
73
+ Benchmark | Iterations | Total time | Overhead
74
+ ------------- | ---------- | ----------- | ----------
75
+ atoi_napi | 20000000 | 1.94s | (baseline)
76
+ atoi_koffi | 20000000 | 3.15s | x1.62
77
+ atoi_node_ffi | 20000000 | 640.49s | x242
78
+
79
+ ## Raylib results
80
+
81
+ This benchmark uses the CPU-based image drawing functions in Raylib. The calls are much heavier than in the atoi benchmark, thus the FFI overhead is reduced. In this implemenetation, the baseline is a full C++ version of the code.
82
+
83
+ ### Linux
84
+
85
+ The results below were measured on my x86_64 Linux machine (AMD® Ryzen™ 7 5800H 16G):
86
+
87
+ Benchmark | Iterations | Total time | Overhead
88
+ --------------- | ---------- | ----------- | ----------
89
+ raylib_cc | 100 | 4.14s | (baseline)
90
+ raylib_koffi | 100 | 6.25s | x1.51
91
+ raylib_node_ffi | 100 | 27.13s | x6.55
92
+
93
+ ### Windows
94
+
95
+ The results below were measured on my x86_64 Windows machine (AMD® Ryzen™ 7 5800H 16G):
96
+
97
+ Benchmark | Iterations | Total time | Overhead
98
+ --------------- | ---------- | ----------- | ----------
99
+ raylib_cc | 100 | 8.39s | (baseline)
100
+ raylib_koffi | 100 | 11.51s | x1.37
101
+ raylib_node_ffi | 100 | 31.47s | x3.8
102
+
43
103
  # Installation
44
104
 
45
105
  ## Windows
@@ -136,17 +196,20 @@ const Font = koffi.struct('Font', {
136
196
  // Fix the path to Raylib DLL if needed
137
197
  let lib = koffi.load('build/raylib' + koffi.extension);
138
198
 
139
- const InitWindow = lib.cdecl('InitWindow', 'void', ['int', 'int', 'string']);
140
- const SetTargetFPS = lib.cdecl('SetTargetFPS', 'void', ['int']);
141
- const GetScreenWidth = lib.cdecl('GetScreenWidth', 'int', []);
142
- const GetScreenHeight = lib.cdecl('GetScreenHeight', 'int', []);
143
- const ClearBackground = lib.cdecl('ClearBackground', 'void', [Color]);
144
- const BeginDrawing = lib.cdecl('BeginDrawing', 'void', []);
145
- const EndDrawing = lib.cdecl('EndDrawing', 'void', []);
146
- const WindowShouldClose = lib.cdecl('WindowShouldClose', 'bool', []);
147
- const GetFontDefault = lib.cdecl('GetFontDefault', Font, []);
148
- const MeasureTextEx = lib.cdecl('MeasureTextEx', Vector2, [Font, 'string', 'float', 'float']);
149
- const DrawTextEx = lib.cdecl('DrawTextEx', 'void', [Font, 'string', Vector2, 'float', 'float', Color]);
199
+ // Classic function declaration
200
+ const InitWindow = lib.func('InitWindow', 'void', ['int', 'int', 'string']);
201
+ const SetTargetFPS = lib.func('SetTargetFPS', 'void', ['int']);
202
+ const GetScreenWidth = lib.func('GetScreenWidth', 'int', []);
203
+ const GetScreenHeight = lib.func('GetScreenHeight', 'int', []);
204
+ const ClearBackground = lib.func('ClearBackground', 'void', [Color]);
205
+
206
+ // Prototype parser
207
+ const BeginDrawing = lib.func('void BeginDrawing()');
208
+ const EndDrawing = lib.func('void EndDrawing()');
209
+ const WindowShouldClose = lib.func('void WindowShouldClose(bool)');
210
+ const GetFontDefault = lib.func('Font GetFontDefault()');
211
+ const MeasureTextEx = lib.func('Vector2 MeasureTextEx(Font, const char *, float, float)');
212
+ const DrawTextEx = lib.func('void DrawTextEx(Font font, const char *text, Vector2 pos, float size, float spacing, Color tint)');
150
213
 
151
214
  InitWindow(800, 600, 'Test Raylib');
152
215
  SetTargetFPS(60);
@@ -200,13 +263,13 @@ MessageBoxA(null, 'Hello', 'Foobar', MB_ICONINFORMATION);
200
263
 
201
264
  Koffi is tested on multiple architectures using emulated (accelerated when possible) QEMU machines. First, you need to install qemu packages, such as `qemu-system` (or even `qemu-system-gui`) on Ubuntu.
202
265
 
203
- These machines are not included directly in this repository (for license and size reasons), but they are available here: https://koromix.dev/files/koffi/
266
+ These machines are not included directly in this repository (for license and size reasons), but they are available here: https://koromix.dev/files/koffi/machines/
204
267
 
205
268
  For example, if you want to run the tests on Debian ARM64, run the following commands:
206
269
 
207
270
  ```sh
208
271
  cd luigi/koffi/test/
209
- wget -q -O- https://koromix.dev/files/koffi/qemu_debian_arm64.tar.zst | zstd -d | tar xv
272
+ wget -q -O- https://koromix.dev/files/koffi/machines/qemu_debian_arm64.tar.zst | zstd -d | tar xv
210
273
  sha256sum -c --ignore-missing registry/sha256sum.txt
211
274
  ```
212
275
 
@@ -251,94 +314,4 @@ Each machine is configured to run a VNC server available locally, which you can
251
314
 
252
315
  ```sh
253
316
  node test.js info debian_x64
254
- ```
255
-
256
- # Benchmarks
257
-
258
- At this stage, two benchmarks are implemented:
259
- * The first one is based around repeated calls to atoi, and has four implementations: one in C++, one calling atoi through an NAPI module, one using Koffi, and one with node-ffi-napi. This is a simple function, thus the JS and FFI overhead is clearly visible.
260
- * The second one is based around Raylib, and will execute much more heavier functions repeatdly. Also in three versions: Koffi, node-ffi-napi and C code.
261
-
262
- In order to run it, go to `koffi/benchmark` and run `../../cnoke/cnoke.js` (or `node ..\..\cnoke\cnoke.js` on Windows) before doing anything else.
263
-
264
- Once this is done, you can execute each implementation, e.g. `build/atoi_cc` or `./atoi_koffi.js`. You can optionally define a custom number of iterations, e.g. `./atoi_koffi.js 10000000`.
265
-
266
- ## atoi results
267
-
268
- Here are some results from 2022-04-24 on Linux on my machine (AMD® Ryzen™ 7 5800H 16G):
269
-
270
- ```sh
271
- $ build/atoi_cc
272
- Iterations: 20000000
273
- Time: 0.24s
274
-
275
- $ ./atoi_napi.js
276
- Iterations: 20000000
277
- Time: 1.10s
278
-
279
- $ ./atoi_koffi.js
280
- Iterations: 20000000
281
- Time: 1.91s
282
-
283
- # Note: the Node-FFI version does a few setTimeout calls to force the GC to run (around 20
284
- # for the example below), without which Node will consume all memory because the GC never appears
285
- # to run, or not enough. It's not ideal but on the other hand it counts as another limitation
286
- # to Node-FFI performance.
287
- $ ./atoi_node_ffi.js
288
- Iterations: 20000000
289
- Time: 640.49s
290
- ```
291
-
292
- And on Windows on the same machine (AMD® Ryzen™ 7 5800H 16G):
293
-
294
- ```sh
295
- $ build\atoi_cc.exe
296
- Iterations: 20000000
297
- Time: 0.25s
298
-
299
- $ node atoi_napi.js
300
- Iterations: 20000000
301
- Time: 1.94s
302
-
303
- $ node atoi_koffi.js
304
- Iterations: 20000000
305
- Time: 3.15s
306
-
307
- $ node atoi_node_ffi.js
308
- Iterations: 20000000
309
- Time: 267.20s
310
- ```
311
-
312
- ## Raylib results
313
-
314
- Here are some results from 2022-04-24 on Linux on my machine (AMD® Ryzen™ 7 5800H 16G):
315
-
316
- ```sh
317
- $ build/raylib_cc
318
- Iterations: 100
319
- Time: 4.14s
320
-
321
- $ ./raylib_koffi.js
322
- Iterations: 100
323
- Time: 6.25s
324
-
325
- $ ./raylib_node_ffi.js
326
- Iterations: 100
327
- Time: 27.13s
328
- ```
329
-
330
- And on Windows on the same machine (AMD® Ryzen™ 7 5800H 16G):
331
-
332
- ```sh
333
- $ build\raylib_cc.exe
334
- Iterations: 100
335
- Time: 8.39s
336
-
337
- $ node raylib_koffi.js
338
- Iterations: 100
339
- Time: 11.51s
340
-
341
- $ node raylib_node_ffi.js
342
- Iterations: 100
343
- Time: 32.47s
344
- ```
317
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "0.9.33",
3
+ "version": "0.9.36",
4
4
  "description": "Fast and simple FFI (foreign function interface) for Node.js",
5
5
  "keywords": [
6
6
  "foreign",
@@ -18,12 +18,12 @@
18
18
  "author": "Niels Martignène <niels.martignene@protonmail.com>",
19
19
  "main": "src/index.js",
20
20
  "scripts": {
21
- "install": "cnoke",
21
+ "install": "cnoke --prebuild https://koromix.dev/files/koffi/builds/{{version}}/koffi_{{platform}}_{{arch}}.tar.gz",
22
22
  "test": "node test/test.js"
23
23
  },
24
24
  "license": "AGPL-3.0",
25
25
  "dependencies": {
26
- "cnoke": "^1.0.1"
26
+ "cnoke": "^1.0.2"
27
27
  },
28
28
  "devDependencies": {
29
29
  "chalk": "^4.1.2",
@@ -31,7 +31,8 @@
31
31
  "minimatch": "^5.0.1",
32
32
  "node-ssh": "^12.0.3",
33
33
  "ref-napi": "^3.0.3",
34
- "ref-struct-di": "^1.1.1"
34
+ "ref-struct-di": "^1.1.1",
35
+ "tar": "^6.1.11"
35
36
  },
36
37
  "files": [
37
38
  "src",
package/src/call_arm32.cc CHANGED
@@ -39,6 +39,7 @@ extern "C" HfaRet ForwardCallXDDDD(const void *func, uint8_t *sp);
39
39
 
40
40
  static bool IsHFA(const TypeInfo *type)
41
41
  {
42
+ #ifdef __ARM_PCS_VFP
42
43
  if (type->primitive != PrimitiveKind::Record)
43
44
  return false;
44
45
 
@@ -54,6 +55,9 @@ static bool IsHFA(const TypeInfo *type)
54
55
  }
55
56
 
56
57
  return true;
58
+ #else
59
+ return false;
60
+ #endif
57
61
  }
58
62
 
59
63
  bool AnalyseFunction(InstanceData *, FunctionInfo *func)
@@ -107,17 +111,23 @@ bool AnalyseFunction(InstanceData *, FunctionInfo *func)
107
111
  case PrimitiveKind::Float64: {
108
112
  Size need = param.type->size / 4;
109
113
 
110
- if (param.variadic) {
111
- if (need <= gpr_avail) {
112
- param.gpr_count = need;
113
- gpr_avail -= need;
114
+ #ifdef __ARM_PCS_VFP
115
+ bool vfp = !param.variadic;
116
+ #else
117
+ bool vfp = false;
118
+ #endif
119
+
120
+ if (vfp) {
121
+ if (need <= vec_avail) {
122
+ param.vec_count = need;
123
+ vec_avail -= need;
114
124
  } else {
115
125
  started_stack = true;
116
126
  }
117
127
  } else {
118
- if (need <= vec_avail) {
119
- param.vec_count = need;
120
- vec_avail -= need;
128
+ if (need <= gpr_avail) {
129
+ param.gpr_count = need;
130
+ gpr_avail -= need;
121
131
  } else {
122
132
  started_stack = true;
123
133
  }
@@ -136,7 +146,7 @@ bool AnalyseFunction(InstanceData *, FunctionInfo *func)
136
146
  vec_avail = 0;
137
147
  started_stack = true;
138
148
  }
139
- } else if (param.type->size) {
149
+ } else {
140
150
  int gpr_count = (param.type->size + 3) / 4;
141
151
 
142
152
  if (gpr_count <= gpr_avail) {
@@ -384,8 +394,12 @@ Napi::Value TranslateCall(InstanceData *instance, const FunctionInfo *func, cons
384
394
  if (RG_UNLIKELY(!call.AllocHeap(param.type->ref->size, 16, &ptr)))
385
395
  return env.Null();
386
396
 
387
- if ((param.directions & 1) && !call.PushObject(obj, param.type->ref, ptr))
388
- return env.Null();
397
+ if (param.directions & 1) {
398
+ if (!call.PushObject(obj, param.type->ref, ptr))
399
+ return env.Null();
400
+ } else {
401
+ memset(ptr, 0, param.type->size);
402
+ }
389
403
  if (param.directions & 2) {
390
404
  OutObject out = {obj, ptr, param.type->ref};
391
405
  out_objects.Append(out);
package/src/call_arm64.cc CHANGED
@@ -353,8 +353,12 @@ Napi::Value TranslateCall(InstanceData *instance, const FunctionInfo *func, cons
353
353
  if (RG_UNLIKELY(!call.AllocHeap(param.type->ref->size, 16, &ptr)))
354
354
  return env.Null();
355
355
 
356
- if ((param.directions & 1) && !call.PushObject(obj, param.type->ref, ptr))
357
- return env.Null();
356
+ if (param.directions & 1) {
357
+ if (!call.PushObject(obj, param.type->ref, ptr))
358
+ return env.Null();
359
+ } else {
360
+ memset(ptr, 0, param.type->size);
361
+ }
358
362
  if (param.directions & 2) {
359
363
  OutObject out = {obj, ptr, param.type->ref};
360
364
  out_objects.Append(out);
@@ -323,8 +323,12 @@ Napi::Value TranslateCall(InstanceData *instance, const FunctionInfo *func, cons
323
323
  if (RG_UNLIKELY(!call.AllocHeap(param.type->ref->size, 16, &ptr)))
324
324
  return env.Null();
325
325
 
326
- if ((param.directions & 1) && !call.PushObject(obj, param.type->ref, ptr))
327
- return env.Null();
326
+ if (param.directions & 1) {
327
+ if (!call.PushObject(obj, param.type->ref, ptr))
328
+ return env.Null();
329
+ } else {
330
+ memset(ptr, 0, param.type->size);
331
+ }
328
332
  if (param.directions & 2) {
329
333
  OutObject out = {obj, ptr, param.type->ref};
330
334
  out_objects.Append(out);
@@ -157,8 +157,12 @@ Napi::Value TranslateCall(InstanceData *instance, const FunctionInfo *func, cons
157
157
  if (RG_UNLIKELY(!call.AllocHeap(param.type->ref->size, 16, &ptr)))
158
158
  return env.Null();
159
159
 
160
- if ((param.directions & 1) && !call.PushObject(obj, param.type->ref, ptr))
161
- return env.Null();
160
+ if (param.directions & 1) {
161
+ if (!call.PushObject(obj, param.type->ref, ptr))
162
+ return env.Null();
163
+ } else {
164
+ memset(ptr, 0, param.type->size);
165
+ }
162
166
  if (param.directions & 2) {
163
167
  OutObject out = {obj, ptr, param.type->ref};
164
168
  out_objects.Append(out);
package/src/call_x86.cc CHANGED
@@ -65,7 +65,7 @@ bool AnalyseFunction(InstanceData *instance, FunctionInfo *func)
65
65
  func->args_size = params_size + 4 * !func->ret.trivial;
66
66
 
67
67
  switch (func->convention) {
68
- case CallConvention::Default: {
68
+ case CallConvention::Cdecl: {
69
69
  func->decorated_name = Fmt(&instance->str_alloc, "_%1", func->name).ptr;
70
70
  } break;
71
71
  case CallConvention::Stdcall: {
@@ -201,8 +201,12 @@ Napi::Value TranslateCall(InstanceData *instance, const FunctionInfo *func, cons
201
201
  if (RG_UNLIKELY(!call.AllocHeap(param.type->ref->size, 16, &ptr)))
202
202
  return env.Null();
203
203
 
204
- if ((param.directions & 1) && !call.PushObject(obj, param.type->ref, ptr))
205
- return env.Null();
204
+ if (param.directions & 1) {
205
+ if (!call.PushObject(obj, param.type->ref, ptr))
206
+ return env.Null();
207
+ } else {
208
+ memset(ptr, 0, param.type->size);
209
+ }
206
210
  if (param.directions & 2) {
207
211
  OutObject out = {obj, ptr, param.type->ref};
208
212
  out_objects.Append(out);
package/src/ffi.cc CHANGED
@@ -14,6 +14,7 @@
14
14
  #include "vendor/libcc/libcc.hh"
15
15
  #include "ffi.hh"
16
16
  #include "call.hh"
17
+ #include "parser.hh"
17
18
  #include "util.hh"
18
19
 
19
20
  #ifdef _WIN32
@@ -182,23 +183,8 @@ static Napi::Value CreatePointerType(const Napi::CallbackInfo &info)
182
183
  if (!ref)
183
184
  return env.Null();
184
185
 
185
- char name_buf[256];
186
- Fmt(name_buf, "%1%2*", ref->name, ref->primitive == PrimitiveKind::Pointer ? "" : " ");
187
-
188
- TypeInfo *type = instance->types_map.FindValue(name_buf, nullptr);
189
-
190
- if (!type) {
191
- type = instance->types.AppendDefault();
192
-
193
- type->name = DuplicateString(name_buf, &instance->str_alloc).ptr;
194
-
195
- type->primitive = PrimitiveKind::Pointer;
196
- type->size = RG_SIZE(void *);
197
- type->align = RG_SIZE(void *);
198
- type->ref = ref;
199
-
200
- instance->types_map.Set(type);
201
- }
186
+ TypeInfo *type = (TypeInfo *)GetPointerType(instance, ref);
187
+ RG_ASSERT(type);
202
188
 
203
189
  Napi::External<TypeInfo> external = Napi::External<TypeInfo>::New(env, type);
204
190
  SetValueTag(instance, external, &TypeInfoMarker);
@@ -284,7 +270,7 @@ static Napi::Value TranslateVariadicCall(const Napi::CallbackInfo &info)
284
270
  return env.Null();
285
271
  }
286
272
 
287
- for (Size i = func.parameters.len; i < info.Length(); i += 2) {
273
+ for (Size i = func.parameters.len; i < (Size)info.Length(); i += 2) {
288
274
  ParameterInfo param = {};
289
275
 
290
276
  param.type = ResolveType(instance, info[i], &param.directions);
@@ -316,38 +302,33 @@ static Napi::Value TranslateVariadicCall(const Napi::CallbackInfo &info)
316
302
  return TranslateCall(instance, &func, info);
317
303
  }
318
304
 
319
- static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConvention convention, bool variadic)
305
+ static bool ParseClassicFunction(Napi::Env env, Napi::String name, Napi::Value ret,
306
+ Napi::Array parameters, FunctionInfo *func)
320
307
  {
321
- Napi::Env env = info.Env();
322
308
  InstanceData *instance = env.GetInstanceData<InstanceData>();
323
- LibraryHolder *lib = (LibraryHolder *)info.Data();
324
309
 
325
- if (info.Length() < 3) {
326
- ThrowError<Napi::TypeError>(env, "Expected 3 or 4 arguments, not %1", info.Length());
327
- return env.Null();
310
+ #ifdef _WIN32
311
+ if (!name.IsString() && !name.IsNumber()) {
312
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for name, expected string or integer", GetValueType(instance, name));
313
+ return false;
328
314
  }
329
- if (!info[0].IsString()) {
330
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value for filename, expected string", GetValueType(instance, info[0]));
331
- return env.Null();
315
+ #else
316
+ if (!name.IsString()) {
317
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for name, expected string", GetValueType(instance, name));
318
+ return false;
332
319
  }
320
+ #endif
333
321
 
334
- FunctionInfo *func = new FunctionInfo();
335
- RG_DEFER_N(func_guard) { delete func; };
336
-
337
- std::string name = ((Napi::Value)info[0u]).As<Napi::String>();
338
- func->name = DuplicateString(name.c_str(), &instance->str_alloc).ptr;
339
- func->lib = lib->Ref();
340
- func->convention = convention;
322
+ func->name = DuplicateString(std::string(name).c_str(), &instance->str_alloc).ptr;
341
323
 
342
- func->ret.type = ResolveType(instance, info[1u]);
324
+ func->ret.type = ResolveType(instance, ret);
343
325
  if (!func->ret.type)
344
- return env.Null();
345
- if (!((Napi::Value)info[2u]).IsArray()) {
346
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value for parameters of '%2', expected an array", GetValueType(instance, (Napi::Value)info[1u]), func->name);
347
- return env.Null();
326
+ return false;
327
+ if (!parameters.IsArray()) {
328
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for parameters of '%2', expected an array", GetValueType(instance, parameters), func->name);
329
+ return false;
348
330
  }
349
331
 
350
- Napi::Array parameters = ((Napi::Value)info[2u]).As<Napi::Array>();
351
332
  Size parameters_len = parameters.Length();
352
333
 
353
334
  if (parameters_len) {
@@ -357,11 +338,6 @@ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConve
357
338
  func->variadic = true;
358
339
  parameters_len--;
359
340
  }
360
-
361
- if (!variadic && func->variadic) {
362
- LogError("Call convention '%1' does not support variadic functions, ignoring");
363
- func->convention = CallConvention::Default;
364
- }
365
341
  }
366
342
 
367
343
  for (uint32_t j = 0; j < parameters_len; j++) {
@@ -369,19 +345,19 @@ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConve
369
345
 
370
346
  param.type = ResolveType(instance, parameters[j], &param.directions);
371
347
  if (!param.type)
372
- return env.Null();
348
+ return false;
373
349
  if (param.type->primitive == PrimitiveKind::Void) {
374
350
  ThrowError<Napi::TypeError>(env, "Type void cannot be used as a parameter");
375
- return env.Null();
351
+ return false;
376
352
  }
377
353
 
378
354
  if (func->parameters.len >= MaxParameters) {
379
355
  ThrowError<Napi::TypeError>(env, "Functions cannot have more than %1 parameters", MaxParameters);
380
- return env.Null();
356
+ return false;
381
357
  }
382
358
  if ((param.directions & 2) && ++func->out_parameters >= MaxOutParameters) {
383
359
  ThrowError<Napi::TypeError>(env, "Functions cannot have more than out %1 parameters", MaxOutParameters);
384
- return env.Null();
360
+ return false;
385
361
  }
386
362
 
387
363
  param.offset = j;
@@ -389,6 +365,40 @@ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConve
389
365
  func->parameters.Append(param);
390
366
  }
391
367
 
368
+ return true;
369
+ }
370
+
371
+ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConvention convention)
372
+ {
373
+ Napi::Env env = info.Env();
374
+ InstanceData *instance = env.GetInstanceData<InstanceData>();
375
+ LibraryHolder *lib = (LibraryHolder *)info.Data();
376
+
377
+ FunctionInfo *func = new FunctionInfo();
378
+ RG_DEFER_N(func_guard) { delete func; };
379
+
380
+ func->lib = lib->Ref();
381
+ func->convention = convention;
382
+
383
+ if (info.Length() >= 3) {
384
+ if (!ParseClassicFunction(env, info[0u].As<Napi::String>(), info[1u], info[2u].As<Napi::Array>(), func))
385
+ return env.Null();
386
+ } else if (info.Length() >= 1) {
387
+ PrototypeParser parser(env);
388
+
389
+ if (!parser.Parse(info[0u].As<Napi::String>(), func))
390
+ return env.Null();
391
+ } else {
392
+ ThrowError<Napi::TypeError>(env, "Expected 1 or 3 arguments, not %1", info.Length());
393
+ return env.Null();
394
+ }
395
+
396
+ if (func->convention != CallConvention::Cdecl && func->variadic) {
397
+ LogError("Call convention '%1' does not support variadic functions, ignoring",
398
+ CallConventionNames[(int)func->convention]);
399
+ func->convention = CallConvention::Cdecl;
400
+ }
401
+
392
402
  if (!AnalyseFunction(instance, func))
393
403
  return env.Null();
394
404
  if (func->variadic) {
@@ -397,11 +407,18 @@ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConve
397
407
  }
398
408
 
399
409
  #ifdef _WIN32
400
- if (func->decorated_name) {
401
- func->func = (void *)GetProcAddress((HMODULE)lib->module, func->decorated_name);
402
- }
403
- if (!func->func) {
404
- func->func = (void *)GetProcAddress((HMODULE)lib->module, func->name);
410
+ if (info[0].IsString()) {
411
+ if (func->decorated_name) {
412
+ func->func = (void *)GetProcAddress((HMODULE)lib->module, func->decorated_name);
413
+ }
414
+ if (!func->func) {
415
+ func->func = (void *)GetProcAddress((HMODULE)lib->module, func->name);
416
+ }
417
+ } else {
418
+ uint16_t ordinal = (uint16_t)info[0].As<Napi::Number>().Uint32Value();
419
+
420
+ func->decorated_name = nullptr;
421
+ func->func = (void *)GetProcAddress((HMODULE)lib->module, (LPCSTR)ordinal);
405
422
  }
406
423
  #else
407
424
  if (func->decorated_name) {
@@ -417,7 +434,7 @@ static Napi::Value FindLibraryFunction(const Napi::CallbackInfo &info, CallConve
417
434
  }
418
435
 
419
436
  Napi::Function::Callback call = func->variadic ? TranslateVariadicCall : TranslateNormalCall;
420
- Napi::Function wrapper = Napi::Function::New(env, call, name.c_str(), (void *)func);
437
+ Napi::Function wrapper = Napi::Function::New(env, call, func->name, (void *)func);
421
438
  wrapper.AddFinalizer([](Napi::Env, FunctionInfo *func) { delete func; }, func);
422
439
  func_guard.Disable();
423
440
 
@@ -481,17 +498,18 @@ static Napi::Value LoadSharedLibrary(const Napi::CallbackInfo &info)
481
498
 
482
499
  Napi::Object obj = Napi::Object::New(env);
483
500
 
484
- #define ADD_CONVENTION(Name, Value, Variadic) \
501
+ #define ADD_CONVENTION(Name, Value) \
485
502
  do { \
486
- const auto wrapper = [](const Napi::CallbackInfo &info) { return FindLibraryFunction(info, (Value), (Variadic)); }; \
503
+ const auto wrapper = [](const Napi::CallbackInfo &info) { return FindLibraryFunction(info, (Value)); }; \
487
504
  Napi::Function func = Napi::Function::New(env, wrapper, (Name), (void *)lib->Ref()); \
488
505
  func.AddFinalizer([](Napi::Env, LibraryHolder *lib) { lib->Unref(); }, lib); \
489
506
  obj.Set((Name), func); \
490
507
  } while (false)
491
508
 
492
- ADD_CONVENTION("cdecl", CallConvention::Default, true);
493
- ADD_CONVENTION("stdcall", CallConvention::Stdcall, false);
494
- ADD_CONVENTION("fastcall", CallConvention::Fastcall, false);
509
+ ADD_CONVENTION("func", CallConvention::Cdecl);
510
+ ADD_CONVENTION("cdecl", CallConvention::Cdecl);
511
+ ADD_CONVENTION("stdcall", CallConvention::Stdcall);
512
+ ADD_CONVENTION("fastcall", CallConvention::Fastcall);
495
513
 
496
514
  #undef ADD_CONVENTION
497
515
 
@@ -585,7 +603,6 @@ static Napi::Object InitBaseTypes(Napi::Env env)
585
603
  RegisterPrimitiveType(instance, "float", PrimitiveKind::Float32, 4, alignof(float));
586
604
  RegisterPrimitiveType(instance, "double", PrimitiveKind::Float64, 8, alignof(double));
587
605
  RegisterPrimitiveType(instance, "string", PrimitiveKind::String, RG_SIZE(void *), alignof(void *));
588
- RegisterPrimitiveType(instance, "str", PrimitiveKind::String, RG_SIZE(void *), alignof(void *));
589
606
 
590
607
  Napi::Object types = Napi::Object::New(env);
591
608
  for (TypeInfo &type: instance->types) {