koffi 1.2.0-alpha.3 → 1.2.0-alpha.4
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/README.md +9 -7
- package/package.json +4 -2
- package/qemu/registry/machines.json +26 -26
- package/src/abi_arm32.cc +63 -73
- package/src/abi_arm32_fwd.S +2 -2
- package/src/abi_arm64.cc +48 -55
- package/src/abi_arm64_fwd.S +2 -2
- package/src/abi_arm64_fwd.asm +2 -2
- package/src/abi_riscv64_fwd.S +2 -2
- package/src/abi_x64_sysv.cc +6 -1
- package/src/abi_x64_sysv_fwd.S +2 -2
- package/src/abi_x64_win.cc +4 -0
- package/src/abi_x64_win_fwd.asm +5 -5
- package/src/abi_x86.cc +2 -0
- package/src/abi_x86_fwd.S +4 -4
- package/src/abi_x86_fwd.asm +4 -4
- package/src/call.cc +49 -46
- package/src/call.hh +6 -6
- package/src/ffi.cc +4 -4
- package/src/util.cc +17 -0
- package/src/util.hh +2 -0
- package/test/async.js +92 -0
- package/test/callbacks.js +87 -0
- package/test/misc.c +25 -5
- package/test/sqlite.js +34 -5
- package/test/sync.js +323 -0
package/src/call.hh
CHANGED
|
@@ -70,7 +70,7 @@ public:
|
|
|
70
70
|
|
|
71
71
|
void Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegisters *out_reg);
|
|
72
72
|
|
|
73
|
-
void
|
|
73
|
+
void DumpForward() const;
|
|
74
74
|
|
|
75
75
|
private:
|
|
76
76
|
template <typename T = void>
|
|
@@ -80,12 +80,12 @@ private:
|
|
|
80
80
|
|
|
81
81
|
const char *PushString(const Napi::Value &value);
|
|
82
82
|
const char16_t *PushString16(const Napi::Value &value);
|
|
83
|
-
bool PushObject(const Napi::Object &obj, const TypeInfo *type, uint8_t *
|
|
84
|
-
bool PushArray(const Napi::Value &obj, const TypeInfo *type, uint8_t *
|
|
83
|
+
bool PushObject(const Napi::Object &obj, const TypeInfo *type, uint8_t *origin, int16_t realign = 0);
|
|
84
|
+
bool PushArray(const Napi::Value &obj, const TypeInfo *type, uint8_t *origin, int16_t realign = 0);
|
|
85
85
|
|
|
86
|
-
void PopObject(Napi::Object obj, const uint8_t *
|
|
87
|
-
Napi::Object PopObject(const uint8_t *
|
|
88
|
-
Napi::Value PopArray(const uint8_t *
|
|
86
|
+
void PopObject(Napi::Object obj, const uint8_t *origin, const TypeInfo *type, int16_t realign = 0);
|
|
87
|
+
Napi::Object PopObject(const uint8_t *origin, const TypeInfo *type, int16_t realign = 0);
|
|
88
|
+
Napi::Value PopArray(const uint8_t *origin, const TypeInfo *type, int16_t realign = 0);
|
|
89
89
|
|
|
90
90
|
Size ReserveTrampoline(const FunctionInfo *proto, Napi::Function func);
|
|
91
91
|
};
|
package/src/ffi.cc
CHANGED
|
@@ -565,7 +565,7 @@ static Napi::Value TranslateNormalCall(const Napi::CallbackInfo &info)
|
|
|
565
565
|
return env.Null();
|
|
566
566
|
|
|
567
567
|
if (instance->debug) {
|
|
568
|
-
call.
|
|
568
|
+
call.DumpForward();
|
|
569
569
|
}
|
|
570
570
|
call.Execute();
|
|
571
571
|
|
|
@@ -633,7 +633,7 @@ static Napi::Value TranslateVariadicCall(const Napi::CallbackInfo &info)
|
|
|
633
633
|
return env.Null();
|
|
634
634
|
|
|
635
635
|
if (instance->debug) {
|
|
636
|
-
call.
|
|
636
|
+
call.DumpForward();
|
|
637
637
|
}
|
|
638
638
|
call.Execute();
|
|
639
639
|
|
|
@@ -664,7 +664,7 @@ public:
|
|
|
664
664
|
|
|
665
665
|
return prepared;
|
|
666
666
|
}
|
|
667
|
-
void
|
|
667
|
+
void DumpForward() { call.DumpForward(); }
|
|
668
668
|
|
|
669
669
|
void Execute() override;
|
|
670
670
|
void OnOK() override;
|
|
@@ -714,7 +714,7 @@ static Napi::Value TranslateAsyncCall(const Napi::CallbackInfo &info)
|
|
|
714
714
|
AsyncCall *async = new AsyncCall(env, instance, func, mem, callback);
|
|
715
715
|
|
|
716
716
|
if (async->Prepare(info) && instance->debug) {
|
|
717
|
-
async->
|
|
717
|
+
async->DumpForward();
|
|
718
718
|
}
|
|
719
719
|
async->Queue();
|
|
720
720
|
|
package/src/util.cc
CHANGED
|
@@ -167,4 +167,21 @@ int IsHFA(const TypeInfo *type, int min, int max)
|
|
|
167
167
|
return hfa ? count : 0;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
void DumpMemory(const char *type, Span<const uint8_t> bytes)
|
|
171
|
+
{
|
|
172
|
+
if (bytes.len) {
|
|
173
|
+
PrintLn(stderr, "%1 at 0x%2 (%3):", type, bytes.ptr, FmtMemSize(bytes.len));
|
|
174
|
+
|
|
175
|
+
for (const uint8_t *ptr = bytes.begin(); ptr < bytes.end();) {
|
|
176
|
+
Print(stderr, " [0x%1 %2 %3] ", FmtArg(ptr).Pad0(-16),
|
|
177
|
+
FmtArg((ptr - bytes.begin()) / sizeof(void *)).Pad(-4),
|
|
178
|
+
FmtArg(ptr - bytes.begin()).Pad(-4));
|
|
179
|
+
for (int i = 0; ptr < bytes.end() && i < (int)sizeof(void *); i++, ptr++) {
|
|
180
|
+
Print(stderr, " %1", FmtHex(*ptr).Pad0(-2));
|
|
181
|
+
}
|
|
182
|
+
PrintLn(stderr);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
170
187
|
}
|
package/src/util.hh
CHANGED
package/test/async.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// This program is free software: you can redistribute it and/or modify
|
|
4
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
5
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
// (at your option) any later version.
|
|
7
|
+
//
|
|
8
|
+
// This program is distributed in the hope that it will be useful,
|
|
9
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
// GNU Affero General Public License for more details.
|
|
12
|
+
//
|
|
13
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
14
|
+
// along with this program. If not, see https://www.gnu.org/licenses/.
|
|
15
|
+
|
|
16
|
+
const koffi = require('./build/koffi.node');
|
|
17
|
+
const assert = require('assert');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const PackedBFG = koffi.pack('PackedBFG', {
|
|
21
|
+
a: 'int8_t',
|
|
22
|
+
b: 'int64_t',
|
|
23
|
+
c: 'char',
|
|
24
|
+
d: 'string',
|
|
25
|
+
e: 'short',
|
|
26
|
+
inner: koffi.pack({
|
|
27
|
+
f: 'float',
|
|
28
|
+
g: 'double'
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
main();
|
|
33
|
+
|
|
34
|
+
async function main() {
|
|
35
|
+
try {
|
|
36
|
+
await test();
|
|
37
|
+
console.log('Success!');
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error(err);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function test() {
|
|
45
|
+
const lib_filename = path.dirname(__filename) + '/build/misc' + koffi.extension;
|
|
46
|
+
const lib = koffi.load(lib_filename);
|
|
47
|
+
|
|
48
|
+
const ConcatenateToInt1 = lib.func('ConcatenateToInt1', 'int64_t', Array(12).fill('int8_t'));
|
|
49
|
+
const MakePackedBFG = lib.func('PackedBFG __fastcall MakePackedBFG(int x, double y, _Out_ PackedBFG *p, const char *str)');
|
|
50
|
+
|
|
51
|
+
let promises = [];
|
|
52
|
+
|
|
53
|
+
// Issue several async calls
|
|
54
|
+
for (let i = 0; i < 64; i++) {
|
|
55
|
+
let p = new Promise((resolve, reject) => {
|
|
56
|
+
try {
|
|
57
|
+
ConcatenateToInt1.async(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7, (err, res) => {
|
|
58
|
+
assert.equal(err, null);
|
|
59
|
+
assert.equal(res, 561239440687n);
|
|
60
|
+
});
|
|
61
|
+
resolve();
|
|
62
|
+
} catch (err) {
|
|
63
|
+
reject(err);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
promises.push(p);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Make a few synchronous calls while the asynchronous ones are still in the air
|
|
70
|
+
assert.equal(ConcatenateToInt1(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
|
|
71
|
+
assert.equal(ConcatenateToInt1(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
|
|
72
|
+
assert.equal(ConcatenateToInt1(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
|
|
73
|
+
|
|
74
|
+
// Async complex call
|
|
75
|
+
{
|
|
76
|
+
let out = {};
|
|
77
|
+
let p = new Promise((resolve, reject) => {
|
|
78
|
+
try {
|
|
79
|
+
MakePackedBFG.async(2, 7, out, '__Hello123456789++++foobarFOOBAR!__', (err, res) => {
|
|
80
|
+
assert.deepEqual(res, { a: 2, b: 4, c: -25, d: 'X/__Hello123456789++++foobarFOOBAR!__/X', e: 54, inner: { f: 14, g: 5 } });
|
|
81
|
+
assert.deepEqual(out, res);
|
|
82
|
+
});
|
|
83
|
+
resolve();
|
|
84
|
+
} catch (err) {
|
|
85
|
+
reject(err);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
promises.push(p);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await Promise.all(promises);
|
|
92
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// This program is free software: you can redistribute it and/or modify
|
|
4
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
5
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
// (at your option) any later version.
|
|
7
|
+
//
|
|
8
|
+
// This program is distributed in the hope that it will be useful,
|
|
9
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
// GNU Affero General Public License for more details.
|
|
12
|
+
//
|
|
13
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
14
|
+
// along with this program. If not, see https://www.gnu.org/licenses/.
|
|
15
|
+
|
|
16
|
+
const koffi = require('./build/koffi.node');
|
|
17
|
+
const assert = require('assert');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const BFG = koffi.struct('BFG', {
|
|
21
|
+
a: 'int8_t',
|
|
22
|
+
b: 'int64_t',
|
|
23
|
+
c: 'char',
|
|
24
|
+
d: 'string',
|
|
25
|
+
e: 'short',
|
|
26
|
+
inner: koffi.struct({
|
|
27
|
+
f: 'float',
|
|
28
|
+
g: 'double'
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const SimpleCallback = koffi.callback('int SimpleCallback(const char *str)');
|
|
33
|
+
const RecursiveCallback = koffi.callback('RecursiveCallback', 'float', ['int', 'string', 'double']);
|
|
34
|
+
const BigCallback = koffi.callback('BFG BigCallback(BFG bfg)');
|
|
35
|
+
|
|
36
|
+
main();
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
try {
|
|
40
|
+
await test();
|
|
41
|
+
console.log('Success!');
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error(err);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function test() {
|
|
49
|
+
const lib_filename = path.dirname(__filename) + '/build/misc' + koffi.extension;
|
|
50
|
+
const lib = koffi.load(lib_filename);
|
|
51
|
+
|
|
52
|
+
const CallJS = lib.func('int CallJS(const char *str, SimpleCallback cb)');
|
|
53
|
+
const CallRecursiveJS = lib.func('float CallRecursiveJS(int i, RecursiveCallback func)');
|
|
54
|
+
const ModifyBFG = lib.func('BFG ModifyBFG(int x, double y, const char *str, BigCallback func, _Out_ BFG *p)');
|
|
55
|
+
|
|
56
|
+
// Simple test similar to README example
|
|
57
|
+
{
|
|
58
|
+
let ret = CallJS('Niels', str => {
|
|
59
|
+
assert.equal(str, 'Hello Niels!');
|
|
60
|
+
return 42;
|
|
61
|
+
});
|
|
62
|
+
assert.equal(ret, 42);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Test with recursion
|
|
66
|
+
{
|
|
67
|
+
let recurse = (i, str, d) => {
|
|
68
|
+
assert.equal(str, 'Hello!');
|
|
69
|
+
assert.equal(d, 42.0);
|
|
70
|
+
return i + (i ? CallRecursiveJS(i - 1, recurse) : 0);
|
|
71
|
+
};
|
|
72
|
+
let total = CallRecursiveJS(9, recurse);
|
|
73
|
+
assert.equal(total, 45);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// And now, with a complex struct
|
|
77
|
+
{
|
|
78
|
+
let out = {};
|
|
79
|
+
let bfg = ModifyBFG(2, 5, "Yo!", bfg => {
|
|
80
|
+
bfg.inner.f *= -1;
|
|
81
|
+
bfg.d = "New!";
|
|
82
|
+
return bfg;
|
|
83
|
+
}, out);
|
|
84
|
+
assert.deepEqual(bfg, { a: 2, b: 4, c: -25, d: 'New!', e: 54, inner: { f: -10, g: 3 } });
|
|
85
|
+
assert.deepEqual(out, { a: 2, b: 4, c: -25, d: 'X/Yo!/X', e: 54, inner: { f: 10, g: 3 } });
|
|
86
|
+
}
|
|
87
|
+
}
|
package/test/misc.c
CHANGED
|
@@ -472,15 +472,35 @@ EXPORT int64_t ThroughInt64IS(SingleI64 s)
|
|
|
472
472
|
return s.v;
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
-
EXPORT
|
|
475
|
+
EXPORT int CallJS(const char *str, int (*cb)(const char *str))
|
|
476
|
+
{
|
|
477
|
+
char buf[64];
|
|
478
|
+
snprintf(buf, sizeof(buf), "Hello %s!", str);
|
|
479
|
+
return cb(buf);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
EXPORT float CallRecursiveJS(int i, float (*func)(int i, const char *str, double d))
|
|
476
483
|
{
|
|
477
484
|
float f = func(i, "Hello!", 42.0);
|
|
478
485
|
return f;
|
|
479
486
|
}
|
|
480
487
|
|
|
481
|
-
EXPORT int
|
|
488
|
+
EXPORT BFG ModifyBFG(int x, double y, const char *str, BFG (*func)(BFG bfg), BFG *p)
|
|
482
489
|
{
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
490
|
+
BFG bfg;
|
|
491
|
+
|
|
492
|
+
static char buf[64];
|
|
493
|
+
snprintf(buf, sizeof(buf), "X/%s/X", str);
|
|
494
|
+
|
|
495
|
+
bfg.a = x;
|
|
496
|
+
bfg.b = x * 2;
|
|
497
|
+
bfg.c = x - 27;
|
|
498
|
+
bfg.d = buf;
|
|
499
|
+
bfg.e = x * 27;
|
|
500
|
+
bfg.inner.f = (float)y * x;
|
|
501
|
+
bfg.inner.g = (double)y - x;
|
|
502
|
+
*p = bfg;
|
|
503
|
+
|
|
504
|
+
bfg = func(bfg);
|
|
505
|
+
return bfg;
|
|
486
506
|
}
|
package/test/sqlite.js
CHANGED
|
@@ -45,32 +45,61 @@ async function test() {
|
|
|
45
45
|
const sqlite3_reset = lib.func('sqlite3_reset', 'int', [sqlite3_stmt]);
|
|
46
46
|
const sqlite3_bind_text = lib.func('sqlite3_bind_text', 'int', [sqlite3_stmt, 'int', 'string', 'int', 'void *']);
|
|
47
47
|
const sqlite3_bind_int = lib.func('sqlite3_bind_int', 'int', [sqlite3_stmt, 'int', 'int']);
|
|
48
|
+
const sqlite3_column_text = lib.func('sqlite3_column_text', 'string', [sqlite3_stmt, 'int']);
|
|
49
|
+
const sqlite3_column_int = lib.func('sqlite3_column_int', 'int', [sqlite3_stmt, 'int']);
|
|
48
50
|
const sqlite3_step = lib.func('sqlite3_step', 'int', [sqlite3_stmt]);
|
|
49
51
|
const sqlite3_finalize = lib.func('sqlite3_finalize', 'int', [sqlite3_stmt]);
|
|
50
52
|
const sqlite3_close_v2 = lib.func('sqlite3_close_v2', 'int', [sqlite3_db]);
|
|
51
53
|
|
|
54
|
+
const SQLITE_OPEN_READWRITE = 0x2;
|
|
55
|
+
const SQLITE_OPEN_CREATE = 0x4;
|
|
56
|
+
const SQLITE_ROW = 100;
|
|
57
|
+
const SQLITE_DONE = 101;
|
|
58
|
+
|
|
52
59
|
let filename = await create_temporary_file(path.join(os.tmpdir(), 'test_sqlite'));
|
|
53
60
|
let db = {};
|
|
54
61
|
|
|
62
|
+
let expected = Array.from(Array(200).keys()).map(i => [`TXT ${i}`, i % 7]);
|
|
63
|
+
|
|
55
64
|
try {
|
|
56
|
-
if (sqlite3_open_v2(filename, db,
|
|
65
|
+
if (sqlite3_open_v2(filename, db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null) != 0)
|
|
57
66
|
throw new Error('Failed to open database');
|
|
58
67
|
if (sqlite3_exec(db, 'CREATE TABLE foo (id INTEGER PRIMARY KEY, bar TEXT, value INT);', null, null, null) != 0)
|
|
59
68
|
throw new Error('Failed to create table');
|
|
60
69
|
|
|
61
70
|
let stmt = {};
|
|
71
|
+
|
|
62
72
|
if (sqlite3_prepare_v2(db, "INSERT INTO foo (bar, value) VALUES (?1, ?2)", -1, stmt, null) != 0)
|
|
63
73
|
throw new Error('Failed to prepare insert statement for table foo');
|
|
64
|
-
for (let
|
|
74
|
+
for (let it of expected) {
|
|
65
75
|
sqlite3_reset(stmt);
|
|
66
76
|
|
|
67
|
-
sqlite3_bind_text(stmt, 1,
|
|
68
|
-
sqlite3_bind_int(stmt, 2,
|
|
77
|
+
sqlite3_bind_text(stmt, 1, it[0], -1, null);
|
|
78
|
+
sqlite3_bind_int(stmt, 2, it[1]);
|
|
69
79
|
|
|
70
|
-
if (sqlite3_step(stmt) !=
|
|
80
|
+
if (sqlite3_step(stmt) != SQLITE_DONE)
|
|
71
81
|
throw new Erorr('Failed to insert new test row');
|
|
72
82
|
}
|
|
73
83
|
sqlite3_finalize(stmt);
|
|
84
|
+
|
|
85
|
+
if (sqlite3_prepare_v2(db, "SELECT id, bar, value FROM foo ORDER BY id", -1, stmt, null) != 0)
|
|
86
|
+
throw new Error('Failed to prepare select statement for table foo');
|
|
87
|
+
for (let i = 0; i < expected.length; i++) {
|
|
88
|
+
let it = expected[i];
|
|
89
|
+
|
|
90
|
+
if (sqlite3_step(stmt) != SQLITE_ROW)
|
|
91
|
+
throw new Error('Missing row');
|
|
92
|
+
|
|
93
|
+
if (sqlite3_column_int(stmt, 0) != i + 1)
|
|
94
|
+
throw new Error('Invalid data');
|
|
95
|
+
if (sqlite3_column_text(stmt, 1) != it[0])
|
|
96
|
+
throw new Error('Invalid data');
|
|
97
|
+
if (sqlite3_column_int(stmt, 2) != it[1])
|
|
98
|
+
throw new Error('Invalid data');
|
|
99
|
+
}
|
|
100
|
+
if (sqlite3_step(stmt) != SQLITE_DONE)
|
|
101
|
+
throw new Error('Unexpected end of statement');
|
|
102
|
+
sqlite3_finalize(stmt);
|
|
74
103
|
} finally {
|
|
75
104
|
sqlite3_close_v2(db);
|
|
76
105
|
fs.unlinkSync(filename);
|