koffi 2.14.0-beta.1 → 2.14.0-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.
- package/build/koffi/darwin_arm64/koffi.node +0 -0
- package/build/koffi/darwin_x64/koffi.node +0 -0
- package/build/koffi/freebsd_arm64/koffi.node +0 -0
- package/build/koffi/freebsd_ia32/koffi.node +0 -0
- package/build/koffi/freebsd_x64/koffi.node +0 -0
- package/build/koffi/linux_arm64/koffi.node +0 -0
- package/build/koffi/linux_armhf/koffi.node +0 -0
- package/build/koffi/linux_ia32/koffi.node +0 -0
- package/build/koffi/linux_loong64/koffi.node +0 -0
- package/build/koffi/linux_riscv64d/koffi.node +0 -0
- package/build/koffi/linux_x64/koffi.node +0 -0
- package/build/koffi/musl_arm64/koffi.node +0 -0
- package/build/koffi/musl_x64/koffi.node +0 -0
- package/build/koffi/openbsd_ia32/koffi.node +0 -0
- package/build/koffi/openbsd_x64/koffi.node +0 -0
- package/build/koffi/win32_arm64/koffi.node +0 -0
- package/build/koffi/win32_ia32/koffi.node +0 -0
- package/build/koffi/win32_x64/koffi.node +0 -0
- package/doc/pages/benchmarks.md +11 -11
- package/doc/static/perf_linux.png +0 -0
- package/doc/static/perf_windows.png +0 -0
- package/index.d.ts +3 -2
- package/index.js +8 -8
- package/indirect.js +8 -8
- package/package.json +1 -1
- package/src/koffi/src/call.cc +66 -17
- package/src/koffi/src/call.hh +4 -2
- package/src/koffi/src/ffi.cc +52 -57
- package/src/koffi/src/util.cc +13 -4
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/doc/pages/benchmarks.md
CHANGED
|
@@ -15,7 +15,7 @@ These results are detailed and explained below, and compared to node-ffi/node-ff
|
|
|
15
15
|
|
|
16
16
|
# Linux x86_64
|
|
17
17
|
|
|
18
|
-
The results presented below were measured on my x86_64 Linux machine (
|
|
18
|
+
The results presented below were measured on my x86_64 Linux machine (AMD Ryzen™ 5 2600).
|
|
19
19
|
|
|
20
20
|
## rand results
|
|
21
21
|
|
|
@@ -27,9 +27,9 @@ This test is based around repeated calls to a simple standard C function `rand`,
|
|
|
27
27
|
|
|
28
28
|
Benchmark | Iteration time | Relative performance | Overhead
|
|
29
29
|
------------- | -------------- | -------------------- | --------
|
|
30
|
-
rand_napi |
|
|
31
|
-
rand_koffi |
|
|
32
|
-
rand_node_ffi |
|
|
30
|
+
rand_napi | 569 ns | x1.00 | (ref)
|
|
31
|
+
rand_koffi | 855 ns | x0.67 | +50%
|
|
32
|
+
rand_node_ffi | 58730 ns | x0.010 | +10228%
|
|
33
33
|
|
|
34
34
|
Because rand is a pretty small function, the FFI overhead is clearly visible.
|
|
35
35
|
|
|
@@ -39,9 +39,9 @@ This test is similar to the rand one, but it is based on `atoi`, which takes a s
|
|
|
39
39
|
|
|
40
40
|
Benchmark | Iteration time | Relative performance | Overhead
|
|
41
41
|
------------- | -------------- | -------------------- | --------
|
|
42
|
-
atoi_napi |
|
|
43
|
-
atoi_koffi |
|
|
44
|
-
atoi_node_ffi |
|
|
42
|
+
atoi_napi | 1039 ns | x1.00 | (ref)
|
|
43
|
+
atoi_koffi | 1642 ns | x0.63 | +58%
|
|
44
|
+
atoi_node_ffi | 164790 ns | x0.006 | +15767%
|
|
45
45
|
|
|
46
46
|
Because atoi is a pretty small function, the FFI overhead is clearly visible.
|
|
47
47
|
|
|
@@ -54,10 +54,10 @@ This benchmark uses the CPU-based image drawing functions in Raylib. The calls a
|
|
|
54
54
|
|
|
55
55
|
Benchmark | Iteration time | Relative performance | Overhead
|
|
56
56
|
------------------ | -------------- | -------------------- | --------
|
|
57
|
-
raylib_cc |
|
|
58
|
-
raylib_node_raylib |
|
|
59
|
-
raylib_koffi | 28.
|
|
60
|
-
raylib_node_ffi |
|
|
57
|
+
raylib_cc | 17.5 µs | x1.34 | -25%
|
|
58
|
+
raylib_node_raylib | 23.4 µs | x1.00 | (ref)
|
|
59
|
+
raylib_koffi | 28.8 µs | x0.81 | +23%
|
|
60
|
+
raylib_node_ffi | 103.9 µs | x0.23 | +344%
|
|
61
61
|
|
|
62
62
|
# Windows x86_64
|
|
63
63
|
|
|
Binary file
|
|
Binary file
|
package/index.d.ts
CHANGED
|
@@ -105,14 +105,15 @@ export class Union {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
export function array(ref: TypeSpec, len: number, hint?: ArrayHint | null): IKoffiCType;
|
|
108
|
-
export function array(ref: TypeSpec, countedBy: string, hint?: ArrayHint | null
|
|
108
|
+
export function array(ref: TypeSpec, countedBy: string, hint?: ArrayHint | null): IKoffiCType;
|
|
109
|
+
export function array(ref: TypeSpec, countedBy: string, maxLen: number, hint?: ArrayHint | null): IKoffiCType;
|
|
109
110
|
|
|
110
111
|
export function opaque(name: string | null | undefined): IKoffiCType;
|
|
111
112
|
export function opaque(): IKoffiCType;
|
|
112
113
|
/** @deprecated */ export function handle(name: string | null | undefined): IKoffiCType;
|
|
113
114
|
/** @deprecated */ export function handle(): IKoffiCType;
|
|
114
115
|
|
|
115
|
-
export function pointer(ref: TypeSpec
|
|
116
|
+
export function pointer(ref: TypeSpec): IKoffiCType;
|
|
116
117
|
export function pointer(ref: TypeSpec, count: number): IKoffiCType;
|
|
117
118
|
export function pointer(name: string | null | undefined, ref: TypeSpec, countedBy?: string | null): IKoffiCType;
|
|
118
119
|
export function pointer(name: string | null | undefined, ref: TypeSpec, count: number): IKoffiCType;
|
package/index.js
CHANGED
|
@@ -4,9 +4,9 @@ var __commonJS = (cb, mod3) => function __require() {
|
|
|
4
4
|
return mod3 || (0, cb[__getOwnPropNames(cb)[0]])((mod3 = { exports: {} }).exports, mod3), mod3.exports;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
//
|
|
7
|
+
// bin/Koffi/package/src/cnoke/src/tools.js
|
|
8
8
|
var require_tools = __commonJS({
|
|
9
|
-
"
|
|
9
|
+
"bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
|
|
10
10
|
"use strict";
|
|
11
11
|
var crypto = require("crypto");
|
|
12
12
|
var fs2 = require("fs");
|
|
@@ -397,12 +397,12 @@ var require_tools = __commonJS({
|
|
|
397
397
|
}
|
|
398
398
|
});
|
|
399
399
|
|
|
400
|
-
//
|
|
400
|
+
// bin/Koffi/package/src/koffi/package.json
|
|
401
401
|
var require_package = __commonJS({
|
|
402
|
-
"
|
|
402
|
+
"bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
|
|
403
403
|
module2.exports = {
|
|
404
404
|
name: "koffi",
|
|
405
|
-
version: "2.14.0-beta.
|
|
405
|
+
version: "2.14.0-beta.3",
|
|
406
406
|
description: "Fast and simple C FFI (foreign function interface) for Node.js",
|
|
407
407
|
keywords: [
|
|
408
408
|
"foreign",
|
|
@@ -443,9 +443,9 @@ var require_package = __commonJS({
|
|
|
443
443
|
}
|
|
444
444
|
});
|
|
445
445
|
|
|
446
|
-
//
|
|
446
|
+
// bin/Koffi/package/src/koffi/src/init.js
|
|
447
447
|
var require_init = __commonJS({
|
|
448
|
-
"
|
|
448
|
+
"bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
|
|
449
449
|
var fs = require("fs");
|
|
450
450
|
var path = require("path");
|
|
451
451
|
var util = require("util");
|
|
@@ -528,7 +528,7 @@ var require_init = __commonJS({
|
|
|
528
528
|
}
|
|
529
529
|
});
|
|
530
530
|
|
|
531
|
-
//
|
|
531
|
+
// bin/Koffi/package/src/koffi/index.js
|
|
532
532
|
var { detect: detect2, init: init2 } = require_init();
|
|
533
533
|
var triplet2 = detect2();
|
|
534
534
|
var native2 = null;
|
package/indirect.js
CHANGED
|
@@ -4,9 +4,9 @@ var __commonJS = (cb, mod3) => function __require() {
|
|
|
4
4
|
return mod3 || (0, cb[__getOwnPropNames(cb)[0]])((mod3 = { exports: {} }).exports, mod3), mod3.exports;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
//
|
|
7
|
+
// bin/Koffi/package/src/cnoke/src/tools.js
|
|
8
8
|
var require_tools = __commonJS({
|
|
9
|
-
"
|
|
9
|
+
"bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
|
|
10
10
|
"use strict";
|
|
11
11
|
var crypto = require("crypto");
|
|
12
12
|
var fs2 = require("fs");
|
|
@@ -397,12 +397,12 @@ var require_tools = __commonJS({
|
|
|
397
397
|
}
|
|
398
398
|
});
|
|
399
399
|
|
|
400
|
-
//
|
|
400
|
+
// bin/Koffi/package/src/koffi/package.json
|
|
401
401
|
var require_package = __commonJS({
|
|
402
|
-
"
|
|
402
|
+
"bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
|
|
403
403
|
module2.exports = {
|
|
404
404
|
name: "koffi",
|
|
405
|
-
version: "2.14.0-beta.
|
|
405
|
+
version: "2.14.0-beta.3",
|
|
406
406
|
description: "Fast and simple C FFI (foreign function interface) for Node.js",
|
|
407
407
|
keywords: [
|
|
408
408
|
"foreign",
|
|
@@ -443,9 +443,9 @@ var require_package = __commonJS({
|
|
|
443
443
|
}
|
|
444
444
|
});
|
|
445
445
|
|
|
446
|
-
//
|
|
446
|
+
// bin/Koffi/package/src/koffi/src/init.js
|
|
447
447
|
var require_init = __commonJS({
|
|
448
|
-
"
|
|
448
|
+
"bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
|
|
449
449
|
var fs = require("fs");
|
|
450
450
|
var path = require("path");
|
|
451
451
|
var util = require("util");
|
|
@@ -528,7 +528,7 @@ var require_init = __commonJS({
|
|
|
528
528
|
}
|
|
529
529
|
});
|
|
530
530
|
|
|
531
|
-
//
|
|
531
|
+
// bin/Koffi/package/src/koffi/indirect.js
|
|
532
532
|
var { detect: detect2, init: init2 } = require_init();
|
|
533
533
|
var triplet2 = detect2();
|
|
534
534
|
var mod2 = init2(triplet2, null);
|
package/package.json
CHANGED
package/src/koffi/src/call.cc
CHANGED
|
@@ -523,6 +523,13 @@ bool CallData::PushObject(Napi::Object obj, const TypeInfo *type, uint8_t *origi
|
|
|
523
523
|
const RecordMember &member = members[i];
|
|
524
524
|
Napi::Value value = obj.Get(member.name);
|
|
525
525
|
|
|
526
|
+
if (member.countedby >= 0) {
|
|
527
|
+
const char *countedby = members[member.countedby].name;
|
|
528
|
+
|
|
529
|
+
if (!CheckDynamicLength(obj, member.type->ref.type->size, countedby, value)) [[unlikely]]
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
|
|
526
533
|
if (value.IsUndefined())
|
|
527
534
|
continue;
|
|
528
535
|
|
|
@@ -708,13 +715,11 @@ bool CallData::PushObject(Napi::Object obj, const TypeInfo *type, uint8_t *origi
|
|
|
708
715
|
case PrimitiveKind::Array: {
|
|
709
716
|
if (value.IsArray()) {
|
|
710
717
|
Napi::Array array = value.As<Napi::Array>();
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
if (!PushNormalArray(array, len, member.type, dest))
|
|
718
|
+
if (!PushNormalArray(array, member.type, member.type->size, dest))
|
|
714
719
|
return false;
|
|
715
720
|
} else if (IsRawBuffer(value)) {
|
|
716
721
|
Span<const uint8_t> buffer = GetRawBuffer(value);
|
|
717
|
-
PushBuffer(buffer, member.type
|
|
722
|
+
PushBuffer(buffer, member.type, dest);
|
|
718
723
|
} else if (value.IsString()) {
|
|
719
724
|
if (!PushStringArray(value, member.type, dest))
|
|
720
725
|
return false;
|
|
@@ -756,15 +761,18 @@ bool CallData::PushObject(Napi::Object obj, const TypeInfo *type, uint8_t *origi
|
|
|
756
761
|
return true;
|
|
757
762
|
}
|
|
758
763
|
|
|
759
|
-
bool CallData::PushNormalArray(Napi::Array array,
|
|
764
|
+
bool CallData::PushNormalArray(Napi::Array array, const TypeInfo *type, Size size, uint8_t *origin)
|
|
760
765
|
{
|
|
761
766
|
RG_ASSERT(array.IsArray());
|
|
762
767
|
|
|
763
768
|
const TypeInfo *ref = type->ref.type;
|
|
769
|
+
Size len = (Size)array.Length();
|
|
770
|
+
Size available = len * ref->size;
|
|
764
771
|
|
|
765
|
-
if (
|
|
766
|
-
|
|
767
|
-
|
|
772
|
+
if (available > size) {
|
|
773
|
+
len = size / ref->size;
|
|
774
|
+
} else {
|
|
775
|
+
MemSet(origin + available, 0, size - available);
|
|
768
776
|
}
|
|
769
777
|
|
|
770
778
|
Size offset = 0;
|
|
@@ -938,13 +946,11 @@ bool CallData::PushNormalArray(Napi::Array array, Size len, const TypeInfo *type
|
|
|
938
946
|
|
|
939
947
|
if (value.IsArray()) {
|
|
940
948
|
Napi::Array array2 = value.As<Napi::Array>();
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
if (!PushNormalArray(array2, len2, ref, dest))
|
|
949
|
+
if (!PushNormalArray(array2, ref, (Size)ref->size, dest))
|
|
944
950
|
return false;
|
|
945
951
|
} else if (IsRawBuffer(value)) {
|
|
946
952
|
Span<const uint8_t> buffer = GetRawBuffer(value);
|
|
947
|
-
PushBuffer(buffer, ref
|
|
953
|
+
PushBuffer(buffer, ref, dest);
|
|
948
954
|
} else if (value.IsString()) {
|
|
949
955
|
if (!PushStringArray(value, ref, dest))
|
|
950
956
|
return false;
|
|
@@ -994,13 +1000,13 @@ bool CallData::PushNormalArray(Napi::Array array, Size len, const TypeInfo *type
|
|
|
994
1000
|
return true;
|
|
995
1001
|
}
|
|
996
1002
|
|
|
997
|
-
void CallData::PushBuffer(Span<const uint8_t> buffer,
|
|
1003
|
+
void CallData::PushBuffer(Span<const uint8_t> buffer, const TypeInfo *type, uint8_t *origin)
|
|
998
1004
|
{
|
|
999
|
-
buffer.len = std::min(buffer.len, size);
|
|
1005
|
+
buffer.len = std::min(buffer.len, (Size)type->size);
|
|
1000
1006
|
|
|
1001
1007
|
// Go fast brrrrrrr :)
|
|
1002
|
-
MemCpy(origin, buffer.ptr,
|
|
1003
|
-
MemSet(origin + buffer.len, 0, (
|
|
1008
|
+
MemCpy(origin, buffer.ptr, buffer.len);
|
|
1009
|
+
MemSet(origin + buffer.len, 0, (Size)type->size - buffer.len);
|
|
1004
1010
|
|
|
1005
1011
|
#define SWAP(CType) \
|
|
1006
1012
|
do { \
|
|
@@ -1124,7 +1130,7 @@ bool CallData::PushPointer(Napi::Value value, const TypeInfo *type, int directio
|
|
|
1124
1130
|
ptr = AllocHeap(size, 16);
|
|
1125
1131
|
|
|
1126
1132
|
if (directions & 1) {
|
|
1127
|
-
if (!PushNormalArray(array,
|
|
1133
|
+
if (!PushNormalArray(array, type, size, ptr))
|
|
1128
1134
|
return false;
|
|
1129
1135
|
} else {
|
|
1130
1136
|
MemSet(ptr, 0, size);
|
|
@@ -1359,6 +1365,49 @@ void CallData::DumpForward(const FunctionInfo *func) const
|
|
|
1359
1365
|
DumpMemory("Heap", heap);
|
|
1360
1366
|
}
|
|
1361
1367
|
|
|
1368
|
+
bool CallData::CheckDynamicLength(Napi::Object obj, Size element, const char *countedby, Napi::Value value)
|
|
1369
|
+
{
|
|
1370
|
+
int64_t expected = -1;
|
|
1371
|
+
int64_t size = -1;
|
|
1372
|
+
|
|
1373
|
+
// Get expected size
|
|
1374
|
+
{
|
|
1375
|
+
Napi::Value by = obj.Get(countedby);
|
|
1376
|
+
|
|
1377
|
+
if (!by.IsNumber() && !by.IsBigInt()) [[unlikely]] {
|
|
1378
|
+
ThrowError<Napi::Error>(env, "Unexpected %1 value for dynamic length, expected number", GetValueType(instance, by));
|
|
1379
|
+
return false;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// If we get anywhere near overflow there are other problems to worry about.
|
|
1383
|
+
// So let's not worry about that.
|
|
1384
|
+
expected = GetNumber<int64_t>(by) * element;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
// Get actual size
|
|
1388
|
+
if (value.IsArray()) {
|
|
1389
|
+
Napi::Array array = value.As<Napi::Array>();
|
|
1390
|
+
size = array.Length() * element;
|
|
1391
|
+
} else if (value.IsTypedArray()) {
|
|
1392
|
+
Napi::TypedArray typed = value.As<Napi::TypedArray>();
|
|
1393
|
+
size = typed.ByteLength();
|
|
1394
|
+
} else if (value.IsArrayBuffer()) {
|
|
1395
|
+
Napi::ArrayBuffer buffer = value.As<Napi::ArrayBuffer>();
|
|
1396
|
+
size = buffer.ByteLength();
|
|
1397
|
+
} else if (!IsNullOrUndefined(value)) {
|
|
1398
|
+
size = element;
|
|
1399
|
+
} else {
|
|
1400
|
+
size = 0;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
if (size != expected) {
|
|
1404
|
+
ThrowError<Napi::Error>(env, "Mismatched dynamic length between '%1' and actual array", countedby);
|
|
1405
|
+
return false;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
return true;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1362
1411
|
static inline Napi::Value GetReferenceValue(Napi::Env env, napi_ref ref)
|
|
1363
1412
|
{
|
|
1364
1413
|
napi_value value;
|
package/src/koffi/src/call.hh
CHANGED
|
@@ -121,8 +121,8 @@ public:
|
|
|
121
121
|
bool PushString32(Napi::Value value, int directions, const char32_t **out_str32);
|
|
122
122
|
Size PushString32Value(Napi::Value value, const char32_t **out_str32);
|
|
123
123
|
bool PushObject(Napi::Object obj, const TypeInfo *type, uint8_t *origin);
|
|
124
|
-
bool PushNormalArray(Napi::Array array,
|
|
125
|
-
void PushBuffer(Span<const uint8_t> buffer,
|
|
124
|
+
bool PushNormalArray(Napi::Array array, const TypeInfo *type, Size size, uint8_t *origin);
|
|
125
|
+
void PushBuffer(Span<const uint8_t> buffer, const TypeInfo *type, uint8_t *origin);
|
|
126
126
|
bool PushStringArray(Napi::Value value, const TypeInfo *type, uint8_t *origin);
|
|
127
127
|
bool PushPointer(Napi::Value value, const TypeInfo *type, int directions, void **out_ptr);
|
|
128
128
|
bool PushCallback(Napi::Value value, const TypeInfo *type, void **out_ptr);
|
|
@@ -138,6 +138,8 @@ private:
|
|
|
138
138
|
template <typename T = uint8_t>
|
|
139
139
|
T *AllocHeap(Size size, Size align);
|
|
140
140
|
|
|
141
|
+
bool CheckDynamicLength(Napi::Object obj, Size element, const char *countedby, Napi::Value value);
|
|
142
|
+
|
|
141
143
|
void PopOutArguments();
|
|
142
144
|
};
|
|
143
145
|
|
package/src/koffi/src/ffi.cc
CHANGED
|
@@ -232,33 +232,6 @@ static bool MapType(Napi::Env env, InstanceData *instance, const TypeInfo *type,
|
|
|
232
232
|
return true;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
static bool CheckDynamicMembers(Napi::Env env, TypeInfo *type)
|
|
236
|
-
{
|
|
237
|
-
for (RecordMember &member: type->members) {
|
|
238
|
-
const char *countedby = member.type->countedby;
|
|
239
|
-
|
|
240
|
-
if (countedby) {
|
|
241
|
-
const RecordMember *by = std::find_if(type->members.begin(), type->members.end(),
|
|
242
|
-
[&](const RecordMember &member) { return TestStr(member.name, countedby); });
|
|
243
|
-
|
|
244
|
-
if (by == member.type->members.end()) {
|
|
245
|
-
ThrowError<Napi::Error>(env, "Record type %1 does not have member '%2'", type->name, countedby);
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
if (!IsInteger(by->type)) {
|
|
249
|
-
ThrowError<Napi::Error>(env, "Dynamic length member %1 is not an integer", countedby);
|
|
250
|
-
return false;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
member.countedby = by - type->members.ptr;
|
|
254
|
-
} else {
|
|
255
|
-
member.countedby = -1;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return true;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
235
|
static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
|
|
263
236
|
{
|
|
264
237
|
Napi::Env env = info.Env();
|
|
@@ -375,6 +348,8 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
|
|
|
375
348
|
size = member.offset + member.type->size;
|
|
376
349
|
type->align = std::max(type->align, align);
|
|
377
350
|
|
|
351
|
+
member.countedby = -1;
|
|
352
|
+
|
|
378
353
|
if (size > instance->config.max_type_size) {
|
|
379
354
|
ThrowError<Napi::Error>(env, "Struct '%1' size is too high (max = %2)", type->name, FmtMemSize(size));
|
|
380
355
|
return env.Null();
|
|
@@ -399,8 +374,30 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
|
|
|
399
374
|
type->members.Append(member);
|
|
400
375
|
}
|
|
401
376
|
|
|
402
|
-
|
|
403
|
-
|
|
377
|
+
for (Size i = 0; i < type->members.len; i++) {
|
|
378
|
+
RecordMember *member = &type->members[i];
|
|
379
|
+
const char *countedby = member->type->countedby;
|
|
380
|
+
|
|
381
|
+
if (countedby) {
|
|
382
|
+
const RecordMember *by = std::find_if(type->members.begin(), type->members.end(),
|
|
383
|
+
[&](const RecordMember &member) { return TestStr(member.name, countedby); });
|
|
384
|
+
|
|
385
|
+
if (by == type->members.end()) {
|
|
386
|
+
ThrowError<Napi::Error>(env, "Record type %1 does not have member '%2'", type->name, countedby);
|
|
387
|
+
return env.Null();
|
|
388
|
+
}
|
|
389
|
+
if (!IsInteger(by->type)) {
|
|
390
|
+
ThrowError<Napi::Error>(env, "Dynamic length member %1 is not an integer", countedby);
|
|
391
|
+
return env.Null();
|
|
392
|
+
}
|
|
393
|
+
if (member->type->primitive == PrimitiveKind::Array && i < type->members.len - 1) {
|
|
394
|
+
ThrowError<Napi::Error>(env, "Flexible array '%1' is not the last member of struct", member->name);
|
|
395
|
+
return env.Null();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
member->countedby = by - type->members.ptr;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
404
401
|
|
|
405
402
|
size = (int32_t)AlignLen(size, type->align);
|
|
406
403
|
if (!size) {
|
|
@@ -537,11 +534,17 @@ static Napi::Value CreateUnionType(const Napi::CallbackInfo &info)
|
|
|
537
534
|
ThrowError<Napi::TypeError>(env, "Type %1 cannot be used as a member (maybe try %1 *)", member.type->name);
|
|
538
535
|
return env.Null();
|
|
539
536
|
}
|
|
537
|
+
if (member.type->countedby) {
|
|
538
|
+
ThrowError<Napi::TypeError>(env, "Cannot use dynamic-length array or pointer inside of union");
|
|
539
|
+
return env.Null();
|
|
540
|
+
}
|
|
540
541
|
|
|
541
542
|
align = align ? align : member.type->align;
|
|
542
543
|
size = std::max(size, member.type->size);
|
|
543
544
|
type->align = std::max(type->align, align);
|
|
544
545
|
|
|
546
|
+
member.countedby = -1;
|
|
547
|
+
|
|
545
548
|
if (TestStr(member.name, "_"))
|
|
546
549
|
continue;
|
|
547
550
|
|
|
@@ -561,9 +564,6 @@ static Napi::Value CreateUnionType(const Napi::CallbackInfo &info)
|
|
|
561
564
|
type->members.Append(member);
|
|
562
565
|
}
|
|
563
566
|
|
|
564
|
-
if (!CheckDynamicMembers(env, type))
|
|
565
|
-
return env.Null();
|
|
566
|
-
|
|
567
567
|
size = (int32_t)AlignLen(size, type->align);
|
|
568
568
|
if (!size) {
|
|
569
569
|
ThrowError<Napi::Error>(env, "Empty union '%1' is not allowed in C", type->name);
|
|
@@ -971,7 +971,7 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
|
|
|
971
971
|
InstanceData *instance = env.GetInstanceData<InstanceData>();
|
|
972
972
|
|
|
973
973
|
if (info.Length() < 2) {
|
|
974
|
-
ThrowError<Napi::TypeError>(env, "Expected 2 to
|
|
974
|
+
ThrowError<Napi::TypeError>(env, "Expected 2 to 4 arguments, got %1", info.Length());
|
|
975
975
|
return env.Null();
|
|
976
976
|
}
|
|
977
977
|
|
|
@@ -979,27 +979,22 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
|
|
|
979
979
|
if (!ref)
|
|
980
980
|
return env.Null();
|
|
981
981
|
|
|
982
|
-
|
|
983
|
-
Napi::Value countedby;
|
|
984
|
-
|
|
985
|
-
if (info[1].IsNumber()) {
|
|
986
|
-
len = info[1].As<Napi::Number>().Int64Value();
|
|
987
|
-
} else if (info[1].IsString()) {
|
|
988
|
-
countedby = info[1];
|
|
982
|
+
bool dynamic = (info.Length() >= 3) && info[1].IsString();
|
|
989
983
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
}
|
|
994
|
-
|
|
984
|
+
if (dynamic && !info[1].IsString()) {
|
|
985
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for countedBy, expected string", GetValueType(instance, info[1]));
|
|
986
|
+
return env.Null();
|
|
987
|
+
}
|
|
988
|
+
if (!info[1 + dynamic].IsNumber()) {
|
|
989
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for %2, expected integer", GetValueType(instance, info[1]), dynamic ? "maxLen" : "len");
|
|
990
|
+
return env.Null();
|
|
995
991
|
}
|
|
996
992
|
|
|
997
|
-
|
|
993
|
+
int64_t len = info[1 + dynamic].As<Napi::Number>().Int64Value();
|
|
994
|
+
|
|
995
|
+
if (len <= 0) {
|
|
998
996
|
ThrowError<Napi::TypeError>(env, "Array length must be positive and non-zero");
|
|
999
997
|
return env.Null();
|
|
1000
|
-
} else if (len < 0) {
|
|
1001
|
-
ThrowError<Napi::TypeError>(env, "Array length must be positive");
|
|
1002
|
-
return env.Null();
|
|
1003
998
|
}
|
|
1004
999
|
if (len > instance->config.max_type_size / ref->size) {
|
|
1005
1000
|
ThrowError<Napi::TypeError>(env, "Array length is too high (max = %1)", instance->config.max_type_size / ref->size);
|
|
@@ -1008,25 +1003,25 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
|
|
|
1008
1003
|
|
|
1009
1004
|
TypeInfo *type = nullptr;
|
|
1010
1005
|
|
|
1011
|
-
if (info.Length() >= 3 && !IsNullOrUndefined(info[2])) {
|
|
1012
|
-
if (!info[2].IsString()) {
|
|
1006
|
+
if (info.Length() >= 3 + dynamic && !IsNullOrUndefined(info[2 + dynamic])) {
|
|
1007
|
+
if (!info[2 + dynamic].IsString()) {
|
|
1013
1008
|
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for hint, expected string", GetValueType(instance, info[2]));
|
|
1014
1009
|
return env.Null();
|
|
1015
1010
|
}
|
|
1016
1011
|
|
|
1017
|
-
std::string
|
|
1012
|
+
std::string str = info[2 + dynamic].As<Napi::String>();
|
|
1018
1013
|
ArrayHint hint = {};
|
|
1019
1014
|
|
|
1020
|
-
if (
|
|
1015
|
+
if (str == "Typed" || str == "typed") {
|
|
1021
1016
|
if (!(ref->flags & (int)TypeFlag::HasTypedArray)) {
|
|
1022
1017
|
ThrowError<Napi::Error>(env, "Array hint 'Typed' cannot be used with type %1", ref->name);
|
|
1023
1018
|
return env.Null();
|
|
1024
1019
|
}
|
|
1025
1020
|
|
|
1026
1021
|
hint = ArrayHint::Typed;
|
|
1027
|
-
} else if (
|
|
1022
|
+
} else if (str == "Array" || str == "array") {
|
|
1028
1023
|
hint = ArrayHint::Array;
|
|
1029
|
-
} else if (
|
|
1024
|
+
} else if (str == "String" || str == "string") {
|
|
1030
1025
|
if (ref->primitive != PrimitiveKind::Int8 && ref->primitive != PrimitiveKind::Int16) {
|
|
1031
1026
|
ThrowError<Napi::Error>(env, "Array hint 'String' can only be used with 8 and 16-bit signed integer types");
|
|
1032
1027
|
return env.Null();
|
|
@@ -1043,8 +1038,8 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
|
|
|
1043
1038
|
type = MakeArrayType(instance, ref, len);
|
|
1044
1039
|
}
|
|
1045
1040
|
|
|
1046
|
-
if (
|
|
1047
|
-
Napi::String str =
|
|
1041
|
+
if (dynamic) {
|
|
1042
|
+
Napi::String str = info[1].As<Napi::String>();
|
|
1048
1043
|
type->countedby = DuplicateString(str.Utf8Value().c_str(), &instance->str_alloc).ptr;
|
|
1049
1044
|
}
|
|
1050
1045
|
|
package/src/koffi/src/util.cc
CHANGED
|
@@ -483,6 +483,9 @@ Napi::External<TypeInfo> WrapType(Napi::Env env, InstanceData *instance, const T
|
|
|
483
483
|
|
|
484
484
|
bool CanPassType(const TypeInfo *type, int directions)
|
|
485
485
|
{
|
|
486
|
+
if (type->countedby)
|
|
487
|
+
return false;
|
|
488
|
+
|
|
486
489
|
if (directions & 2) {
|
|
487
490
|
if (type->primitive == PrimitiveKind::Pointer)
|
|
488
491
|
return true;
|
|
@@ -510,6 +513,9 @@ bool CanPassType(const TypeInfo *type, int directions)
|
|
|
510
513
|
|
|
511
514
|
bool CanReturnType(const TypeInfo *type)
|
|
512
515
|
{
|
|
516
|
+
if (type->countedby)
|
|
517
|
+
return false;
|
|
518
|
+
|
|
513
519
|
if (type->primitive == PrimitiveKind::Void && !TestStr(type->name, "void"))
|
|
514
520
|
return false;
|
|
515
521
|
if (type->primitive == PrimitiveKind::Array)
|
|
@@ -900,7 +906,12 @@ void DecodeObject(Napi::Object obj, const uint8_t *origin, const TypeInfo *type)
|
|
|
900
906
|
case PrimitiveKind::Array: {
|
|
901
907
|
if (member.countedby >= 0) {
|
|
902
908
|
const RecordMember &by = type->members[member.countedby];
|
|
909
|
+
|
|
903
910
|
uint32_t len = DecodeDynamicLength(origin, by);
|
|
911
|
+
uint32_t max = member.type->size / member.type->ref.type->size;
|
|
912
|
+
|
|
913
|
+
// Silently truncate result
|
|
914
|
+
len = std::min(len, max);
|
|
904
915
|
|
|
905
916
|
Napi::Value value = DecodeArray(env, src, member.type, len);
|
|
906
917
|
obj.Set(member.name, value);
|
|
@@ -1628,13 +1639,11 @@ bool Encode(Napi::Env env, uint8_t *origin, Napi::Value value, const TypeInfo *t
|
|
|
1628
1639
|
case PrimitiveKind::Array: {
|
|
1629
1640
|
if (value.IsArray()) {
|
|
1630
1641
|
Napi::Array array = value.As<Napi::Array>();
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
if (!call.PushNormalArray(array, len, type, origin))
|
|
1642
|
+
if (!call.PushNormalArray(array, type, type->size, origin))
|
|
1634
1643
|
return false;
|
|
1635
1644
|
} else if (IsRawBuffer(value)) {
|
|
1636
1645
|
Span<const uint8_t> buffer = GetRawBuffer(value);
|
|
1637
|
-
call.PushBuffer(buffer, type
|
|
1646
|
+
call.PushBuffer(buffer, type, origin);
|
|
1638
1647
|
} else if (value.IsString()) {
|
|
1639
1648
|
if (!call.PushStringArray(value, type, origin))
|
|
1640
1649
|
return false;
|