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 +2 -1
- package/README.md +81 -108
- package/package.json +5 -4
- package/src/call_arm32.cc +24 -10
- package/src/call_arm64.cc +6 -2
- package/src/call_x64_sysv.cc +6 -2
- package/src/call_x64_win.cc +6 -2
- package/src/call_x86.cc +7 -3
- package/src/ffi.cc +78 -61
- package/src/ffi.hh +2 -2
- package/src/parser.cc +246 -0
- package/src/parser.hh +63 -0
- package/src/util.cc +23 -0
- package/src/util.hh +3 -0
- package/test/registry/machines.json +70 -0
- package/test/test.js +115 -7
- package/test/tests/misc.js +16 -16
- package/test/tests/raylib.js +8 -8
- package/test/tests/sqlite.js +9 -9
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
|
-
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
const
|
|
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.
|
|
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.
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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 <=
|
|
119
|
-
param.
|
|
120
|
-
|
|
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
|
|
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 (
|
|
388
|
-
|
|
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 (
|
|
357
|
-
|
|
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);
|
package/src/call_x64_sysv.cc
CHANGED
|
@@ -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 (
|
|
327
|
-
|
|
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);
|
package/src/call_x64_win.cc
CHANGED
|
@@ -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 (
|
|
161
|
-
|
|
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::
|
|
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 (
|
|
205
|
-
|
|
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
|
-
|
|
186
|
-
|
|
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], ¶m.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::
|
|
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
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
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,
|
|
324
|
+
func->ret.type = ResolveType(instance, ret);
|
|
343
325
|
if (!func->ret.type)
|
|
344
|
-
return
|
|
345
|
-
if (!
|
|
346
|
-
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for parameters of '%2', expected an array", GetValueType(instance,
|
|
347
|
-
return
|
|
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], ¶m.directions);
|
|
371
347
|
if (!param.type)
|
|
372
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
func->func
|
|
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
|
|
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
|
|
501
|
+
#define ADD_CONVENTION(Name, Value) \
|
|
485
502
|
do { \
|
|
486
|
-
const auto wrapper = [](const Napi::CallbackInfo &info) { return FindLibraryFunction(info, (Value)
|
|
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("
|
|
493
|
-
ADD_CONVENTION("
|
|
494
|
-
ADD_CONVENTION("
|
|
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) {
|