koffi 2.5.21-beta.1 → 2.5.21-beta.2

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.
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/contribute.md CHANGED
@@ -129,16 +129,24 @@ node qemu.js info debian_x64
129
129
 
130
130
  ## Making a release
131
131
 
132
- First, change the version numbers in `package.json`. Then publish a new release with the following commands:
132
+ First, you must update the code in three steps:
133
+
134
+ - Change the version numbers in `package.json` (version and stable for stable releases)
135
+ - Add an entry to `CHANGELOG.md` to summarize the changes since last release
136
+ - Commit theses changes with the message *Bump Koffi to X.Y.Z*
137
+
138
+ Once this is done, you can publish a new release with the following commands:
133
139
 
134
140
  ```sh
135
- node qemu.js test
141
+ node qemu.js test # If not done before
136
142
  node qemu.js dist
137
143
 
138
144
  cd build/dist
139
145
  npm publish
140
146
  ```
141
147
 
148
+ Some platforms are emulated so this can take a few minutes until the pre-built binaries are ready. Go grab a cup of coffee, come back and execute the `npm publish` command!
149
+
142
150
  ## Code style
143
151
 
144
152
  Koffi is programmed in a mix of C++ and assembly code (architecture-specific code). It uses [node-addon-api](https://github.com/nodejs/node-addon-api) (C++ N-API wrapper) to interact with Node.js.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "2.5.21-beta.1",
3
+ "version": "2.5.21-beta.2",
4
4
  "stable": "2.5.20",
5
5
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
6
6
  "keywords": [
package/src/index.js CHANGED
@@ -378,7 +378,7 @@ var require_package = __commonJS({
378
378
  "build/dist/src/koffi/package.json"(exports2, module2) {
379
379
  module2.exports = {
380
380
  name: "koffi",
381
- version: "2.5.21-beta.1",
381
+ version: "2.5.21-beta.2",
382
382
  stable: "2.5.20",
383
383
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
384
384
  keywords: [
@@ -1165,6 +1165,61 @@ Size CallData::PushIndirectString(Napi::Array array, const TypeInfo *ref, uint8_
1165
1165
  }
1166
1166
  }
1167
1167
 
1168
+ void *CallData::ReserveTrampoline(const FunctionInfo *proto, Napi::Function func)
1169
+ {
1170
+ if (!InitAsyncBroker(env, instance)) [[unlikely]]
1171
+ return nullptr;
1172
+
1173
+ int16_t idx;
1174
+ {
1175
+ std::lock_guard<std::mutex> lock(shared.mutex);
1176
+
1177
+ if (!shared.available.len) [[unlikely]] {
1178
+ ThrowError<Napi::Error>(env, "Too many callbacks are in use (max = %1)", MaxTrampolines);
1179
+ return env.Null();
1180
+ }
1181
+ if (!used_trampolines.Available()) [[unlikely]] {
1182
+ ThrowError<Napi::Error>(env, "This call uses too many temporary callbacks (max = %1)", RG_LEN(used_trampolines.data));
1183
+ return env.Null();
1184
+ }
1185
+
1186
+ idx = shared.available.data[--shared.available.len];
1187
+ used_trampolines.Append(idx);
1188
+ }
1189
+
1190
+ TrampolineInfo *trampoline = &shared.trampolines[idx];
1191
+
1192
+ trampoline->instance = instance;
1193
+ trampoline->proto = proto;
1194
+ trampoline->func.Reset(func, 1);
1195
+ trampoline->recv.Reset();
1196
+ trampoline->generation = (int32_t)mem->generation;
1197
+
1198
+ void *ptr = GetTrampoline(idx, proto);
1199
+
1200
+ return ptr;
1201
+ }
1202
+
1203
+ void CallData::DumpForward(const FunctionInfo *func) const
1204
+ {
1205
+ PrintLn(stderr, "%!..+---- %1 (%2) ----%!0", func->name, CallConventionNames[(int)func->convention]);
1206
+
1207
+ if (func->parameters.len) {
1208
+ PrintLn(stderr, "Parameters:");
1209
+ for (Size i = 0; i < func->parameters.len; i++) {
1210
+ const ParameterInfo &param = func->parameters[i];
1211
+ PrintLn(stderr, " %1 = %2 (%3)", i, param.type->name, FmtMemSize(param.type->size));
1212
+ }
1213
+ }
1214
+ PrintLn(stderr, "Return: %1 (%2)", func->ret.type->name, FmtMemSize(func->ret.type->size));
1215
+
1216
+ Span<const uint8_t> stack = MakeSpan(mem->stack.end(), old_stack_mem.end() - mem->stack.end());
1217
+ Span<const uint8_t> heap = MakeSpan(old_heap_mem.ptr, mem->heap.ptr - old_heap_mem.ptr);
1218
+
1219
+ DumpMemory("Stack", stack);
1220
+ DumpMemory("Heap", heap);
1221
+ }
1222
+
1168
1223
  static inline Napi::Value GetReferenceValue(Napi::Env env, napi_ref ref)
1169
1224
  {
1170
1225
  napi_value value;
@@ -1234,59 +1289,4 @@ void CallData::PopOutArguments()
1234
1289
  }
1235
1290
  }
1236
1291
 
1237
- void *CallData::ReserveTrampoline(const FunctionInfo *proto, Napi::Function func)
1238
- {
1239
- if (!InitAsyncBroker(env, instance)) [[unlikely]]
1240
- return nullptr;
1241
-
1242
- int16_t idx;
1243
- {
1244
- std::lock_guard<std::mutex> lock(shared.mutex);
1245
-
1246
- if (!shared.available.len) [[unlikely]] {
1247
- ThrowError<Napi::Error>(env, "Too many callbacks are in use (max = %1)", MaxTrampolines);
1248
- return env.Null();
1249
- }
1250
- if (!used_trampolines.Available()) [[unlikely]] {
1251
- ThrowError<Napi::Error>(env, "This call uses too many temporary callbacks (max = %1)", RG_LEN(used_trampolines.data));
1252
- return env.Null();
1253
- }
1254
-
1255
- idx = shared.available.data[--shared.available.len];
1256
- used_trampolines.Append(idx);
1257
- }
1258
-
1259
- TrampolineInfo *trampoline = &shared.trampolines[idx];
1260
-
1261
- trampoline->instance = instance;
1262
- trampoline->proto = proto;
1263
- trampoline->func.Reset(func, 1);
1264
- trampoline->recv.Reset();
1265
- trampoline->generation = (int32_t)mem->generation;
1266
-
1267
- void *ptr = GetTrampoline(idx, proto);
1268
-
1269
- return ptr;
1270
- }
1271
-
1272
- void CallData::DumpForward(const FunctionInfo *func) const
1273
- {
1274
- PrintLn(stderr, "%!..+---- %1 (%2) ----%!0", func->name, CallConventionNames[(int)func->convention]);
1275
-
1276
- if (func->parameters.len) {
1277
- PrintLn(stderr, "Parameters:");
1278
- for (Size i = 0; i < func->parameters.len; i++) {
1279
- const ParameterInfo &param = func->parameters[i];
1280
- PrintLn(stderr, " %1 = %2 (%3)", i, param.type->name, FmtMemSize(param.type->size));
1281
- }
1282
- }
1283
- PrintLn(stderr, "Return: %1 (%2)", func->ret.type->name, FmtMemSize(func->ret.type->size));
1284
-
1285
- Span<const uint8_t> stack = MakeSpan(mem->stack.end(), old_stack_mem.end() - mem->stack.end());
1286
- Span<const uint8_t> heap = MakeSpan(old_heap_mem.ptr, mem->heap.ptr - old_heap_mem.ptr);
1287
-
1288
- DumpMemory("Stack", stack);
1289
- DumpMemory("Heap", heap);
1290
- }
1291
-
1292
1292
  }
@@ -113,12 +113,6 @@ public:
113
113
 
114
114
  void DumpForward(const FunctionInfo *func) const;
115
115
 
116
- private:
117
- template <typename T>
118
- bool AllocStack(Size size, Size align, T **out_ptr);
119
- template <typename T = uint8_t>
120
- T *AllocHeap(Size size, Size align);
121
-
122
116
  bool PushString(Napi::Value value, int directions, const char **out_str);
123
117
  Size PushStringValue(Napi::Value value, const char **out_str);
124
118
  bool PushString16(Napi::Value value, int directions, const char16_t **out_str16);
@@ -130,9 +124,15 @@ private:
130
124
  bool PushPointer(Napi::Value value, const TypeInfo *type, int directions, void **out_ptr);
131
125
  Size PushIndirectString(Napi::Array array, const TypeInfo *ref, uint8_t **out_ptr);
132
126
 
133
- void PopOutArguments();
134
-
135
127
  void *ReserveTrampoline(const FunctionInfo *proto, Napi::Function func);
128
+
129
+ private:
130
+ template <typename T>
131
+ bool AllocStack(Size size, Size align, T **out_ptr);
132
+ template <typename T = uint8_t>
133
+ T *AllocHeap(Size size, Size align);
134
+
135
+ void PopOutArguments();
136
136
  };
137
137
 
138
138
  template <typename T>
@@ -1897,6 +1897,39 @@ static Napi::Value CallPointerSync(const Napi::CallbackInfo &info)
1897
1897
  : TranslateNormalCall(proto, ptr, info);
1898
1898
  }
1899
1899
 
1900
+ static Napi::Value EncodeValue(const Napi::CallbackInfo &info)
1901
+ {
1902
+ Napi::Env env = info.Env();
1903
+
1904
+ bool has_offset = (info.Length() >= 2 && info[1].IsNumber());
1905
+ bool has_len = (info.Length() >= 4u + has_offset && info[2u + has_offset].IsNumber());
1906
+
1907
+ if (info.Length() < 3u + has_offset) [[unlikely]] {
1908
+ ThrowError<Napi::TypeError>(env, "Expected %1 to 5 arguments, got %2", 3 + has_offset, info.Length());
1909
+ return env.Null();
1910
+ }
1911
+
1912
+ const TypeInfo *type = ResolveType(info[1u + has_offset]);
1913
+ if (!type) [[unlikely]]
1914
+ return env.Null();
1915
+
1916
+ Napi::Value ref = info[0];
1917
+ int64_t offset = has_offset ? info[1].As<Napi::Number>().Int64Value() : 0;
1918
+ Napi::Value value = info[2u + has_offset + has_len];
1919
+
1920
+ if (has_len) {
1921
+ Size len = info[2u + has_offset].As<Napi::Number>();
1922
+
1923
+ if (!Encode(ref, offset, value, type, &len))
1924
+ return env.Null();
1925
+ } else {
1926
+ if (!Encode(ref, offset, value, type))
1927
+ return env.Null();
1928
+ }
1929
+
1930
+ return env.Undefined();
1931
+ }
1932
+
1900
1933
  void LibraryHolder::Unload()
1901
1934
  {
1902
1935
  #ifdef _WIN32
@@ -1980,6 +2013,11 @@ bool InitAsyncBroker(Napi::Env env, InstanceData *instance)
1980
2013
  return true;
1981
2014
  }
1982
2015
 
2016
+ CallData *GetThreadCall()
2017
+ {
2018
+ return exec_call;
2019
+ }
2020
+
1983
2021
  static void RegisterPrimitiveType(Napi::Env env, Napi::Object map, std::initializer_list<const char *> names,
1984
2022
  PrimitiveKind primitive, int32_t size, int16_t align, const char *ref = nullptr)
1985
2023
  {
@@ -2193,6 +2231,7 @@ static Napi::Object InitModule(Napi::Env env, Napi::Object exports)
2193
2231
  exports.Set("decode", Napi::Function::New(env, DecodeValue));
2194
2232
  exports.Set("address", Napi::Function::New(env, GetPointerAddress));
2195
2233
  exports.Set("call", Napi::Function::New(env, CallPointerSync));
2234
+ exports.Set("encode", Napi::Function::New(env, EncodeValue));
2196
2235
 
2197
2236
  exports.Set("reset", Napi::Function::New(env, ResetKoffi));
2198
2237
 
@@ -99,6 +99,7 @@ static const char *const PrimitiveKindNames[] = {
99
99
  struct TypeInfo;
100
100
  struct RecordMember;
101
101
  struct FunctionInfo;
102
+ class CallData;
102
103
 
103
104
  typedef void DisposeFunc (Napi::Env env, const TypeInfo *type, const void *ptr);
104
105
 
@@ -342,5 +343,6 @@ Napi::Value TranslateVariadicCall(const Napi::CallbackInfo &info);
342
343
  Napi::Value TranslateAsyncCall(const Napi::CallbackInfo &info);
343
344
 
344
345
  bool InitAsyncBroker(Napi::Env env, InstanceData *instance);
346
+ CallData *GetThreadCall();
345
347
 
346
348
  }
@@ -1193,6 +1193,206 @@ Napi::Value Decode(Napi::Env env, const uint8_t *ptr, const TypeInfo *type, cons
1193
1193
  return env.Null();
1194
1194
  }
1195
1195
 
1196
+ bool Encode(Napi::Value ref, Size offset, Napi::Value value, const TypeInfo *type, const Size *len)
1197
+ {
1198
+ Napi::Env env = ref.Env();
1199
+ InstanceData *instance = env.GetInstanceData<InstanceData>();
1200
+
1201
+ uint8_t *ptr = nullptr;
1202
+
1203
+ if (ref.IsExternal()) {
1204
+ Napi::External<void> external = ref.As<Napi::External<void>>();
1205
+ ptr = (uint8_t *)external.Data();
1206
+ } else if (IsRawBuffer(ref)) {
1207
+ Span<uint8_t> buffer = GetRawBuffer(ref);
1208
+
1209
+ if (buffer.len - offset < type->size) [[unlikely]] {
1210
+ ThrowError<Napi::Error>(env, "Expected buffer with size superior or equal to type %1 (%2 bytes)",
1211
+ type->name, type->size + offset);
1212
+ return env.Null();
1213
+ }
1214
+
1215
+ ptr = (uint8_t *)buffer.ptr;
1216
+ } else {
1217
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for reference, expected external or TypedArray", GetValueType(instance, value));
1218
+ return env.Null();
1219
+ }
1220
+
1221
+ if (!ptr) [[unlikely]] {
1222
+ ThrowError<Napi::Error>(env, "Cannot encode data in NULL pointer");
1223
+ return env.Null();
1224
+ }
1225
+ ptr += offset;
1226
+
1227
+ return Encode(env, ptr, value, type, len);
1228
+ }
1229
+
1230
+ bool Encode(Napi::Env env, uint8_t *origin, Napi::Value value, const TypeInfo *type, const Size *len)
1231
+ {
1232
+ InstanceData *instance = env.GetInstanceData<InstanceData>();
1233
+ CallData *call = GetThreadCall();
1234
+
1235
+ if (len && type->primitive != PrimitiveKind::String &&
1236
+ type->primitive != PrimitiveKind::String16 &&
1237
+ type->primitive != PrimitiveKind::Prototype) {
1238
+ if (*len < 0) [[unlikely]] {
1239
+ ThrowError<Napi::TypeError>(env, "Automatic (negative) length is only supported when decoding");
1240
+ return env.Null();
1241
+ }
1242
+
1243
+ type = MakeArrayType(instance, type, *len);
1244
+ }
1245
+
1246
+ if (!call) [[unlikely]] {
1247
+ ThrowError<Napi::Error>(env, "koffi.encode() can only be used inside callbacks");
1248
+ return false;
1249
+ }
1250
+
1251
+ #define PUSH_INTEGER(CType) \
1252
+ do { \
1253
+ if (!value.IsNumber() && !value.IsBigInt()) [[unlikely]] { \
1254
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected number", GetValueType(instance, value)); \
1255
+ return false; \
1256
+ } \
1257
+ \
1258
+ CType v = GetNumber<CType>(value); \
1259
+ *(CType *)origin = v; \
1260
+ } while (false)
1261
+ #define PUSH_INTEGER_SWAP(CType) \
1262
+ do { \
1263
+ if (!value.IsNumber() && !value.IsBigInt()) [[unlikely]] { \
1264
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected number", GetValueType(instance, value)); \
1265
+ return false; \
1266
+ } \
1267
+ \
1268
+ CType v = GetNumber<CType>(value); \
1269
+ *(CType *)origin = ReverseBytes(v); \
1270
+ } while (false)
1271
+
1272
+ switch (type->primitive) {
1273
+ case PrimitiveKind::Void: { RG_UNREACHABLE(); } break;
1274
+
1275
+ case PrimitiveKind::Bool: {
1276
+ if (!value.IsBoolean()) [[unlikely]] {
1277
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected boolean", GetValueType(instance, value));
1278
+ return false;
1279
+ }
1280
+
1281
+ bool b = value.As<Napi::Boolean>();
1282
+ *(bool *)origin = b;
1283
+ } break;
1284
+ case PrimitiveKind::Int8: { PUSH_INTEGER(int8_t); } break;
1285
+ case PrimitiveKind::UInt8: { PUSH_INTEGER(uint8_t); } break;
1286
+ case PrimitiveKind::Int16: { PUSH_INTEGER(int16_t); } break;
1287
+ case PrimitiveKind::Int16S: { PUSH_INTEGER_SWAP(int16_t); } break;
1288
+ case PrimitiveKind::UInt16: { PUSH_INTEGER(uint16_t); } break;
1289
+ case PrimitiveKind::UInt16S: { PUSH_INTEGER_SWAP(uint16_t); } break;
1290
+ case PrimitiveKind::Int32: { PUSH_INTEGER(int32_t); } break;
1291
+ case PrimitiveKind::Int32S: { PUSH_INTEGER_SWAP(int32_t); } break;
1292
+ case PrimitiveKind::UInt32: { PUSH_INTEGER(uint32_t); } break;
1293
+ case PrimitiveKind::UInt32S: { PUSH_INTEGER_SWAP(uint32_t); } break;
1294
+ case PrimitiveKind::Int64: { PUSH_INTEGER(int64_t); } break;
1295
+ case PrimitiveKind::Int64S: { PUSH_INTEGER_SWAP(int64_t); } break;
1296
+ case PrimitiveKind::UInt64: { PUSH_INTEGER(uint64_t); } break;
1297
+ case PrimitiveKind::UInt64S: { PUSH_INTEGER_SWAP(uint64_t); } break;
1298
+ case PrimitiveKind::String: {
1299
+ const char *str;
1300
+ if (!call->PushString(value, 1, &str)) [[unlikely]]
1301
+ return false;
1302
+ *(const char **)origin = str;
1303
+ } break;
1304
+ case PrimitiveKind::String16: {
1305
+ const char16_t *str16;
1306
+ if (!call->PushString16(value, 1, &str16)) [[unlikely]]
1307
+ return false;
1308
+ *(const char16_t **)origin = str16;
1309
+ } break;
1310
+ case PrimitiveKind::Pointer: {
1311
+ void *ptr;
1312
+ if (!call->PushPointer(value, type, 1, &ptr)) [[unlikely]]
1313
+ return false;
1314
+ *(void **)origin = ptr;
1315
+ } break;
1316
+ case PrimitiveKind::Record:
1317
+ case PrimitiveKind::Union: {
1318
+ if (!IsObject(value)) [[unlikely]] {
1319
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected object", GetValueType(instance, value));
1320
+ return false;
1321
+ }
1322
+
1323
+ Napi::Object obj = value.As<Napi::Object>();
1324
+
1325
+ if (!call->PushObject(obj, type, origin))
1326
+ return false;
1327
+ } break;
1328
+ case PrimitiveKind::Array: {
1329
+ if (value.IsArray()) {
1330
+ Napi::Array array = value.As<Napi::Array>();
1331
+ Size len = (Size)type->size / type->ref.type->size;
1332
+
1333
+ if (!call->PushNormalArray(array, len, type, origin))
1334
+ return false;
1335
+ } else if (IsRawBuffer(value)) {
1336
+ Span<const uint8_t> buffer = GetRawBuffer(value);
1337
+
1338
+ if (!call->PushBuffer(buffer, type->size, type, origin))
1339
+ return false;
1340
+ } else if (value.IsString()) {
1341
+ if (!call->PushStringArray(value, type, origin))
1342
+ return false;
1343
+ } else {
1344
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected array", GetValueType(instance, value));
1345
+ return false;
1346
+ }
1347
+ } break;
1348
+ case PrimitiveKind::Float32: {
1349
+ if (!value.IsNumber() && !value.IsBigInt()) [[unlikely]] {
1350
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected number", GetValueType(instance, value));
1351
+ return false;
1352
+ }
1353
+
1354
+ float f = GetNumber<float>(value);
1355
+ *(float *)origin = f;
1356
+ } break;
1357
+ case PrimitiveKind::Float64: {
1358
+ if (!value.IsNumber() && !value.IsBigInt()) [[unlikely]] {
1359
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected number", GetValueType(instance, value));
1360
+ return false;
1361
+ }
1362
+
1363
+ double d = GetNumber<double>(value);
1364
+ *(double *)origin = d;
1365
+ } break;
1366
+ case PrimitiveKind::Callback: {
1367
+ void *ptr;
1368
+
1369
+ if (value.IsFunction()) {
1370
+ Napi::Function func = value.As<Napi::Function>();
1371
+
1372
+ ptr = call->ReserveTrampoline(type->ref.proto, func);
1373
+ if (!ptr) [[unlikely]]
1374
+ return false;
1375
+ } else if (CheckValueTag(instance, value, type->ref.marker)) {
1376
+ ptr = value.As<Napi::External<void>>().Data();
1377
+ } else if (IsNullOrUndefined(value)) {
1378
+ ptr = nullptr;
1379
+ } else {
1380
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected %2", GetValueType(instance, value), type->name);
1381
+ return false;
1382
+ }
1383
+
1384
+ *(void **)origin = ptr;
1385
+ } break;
1386
+
1387
+ case PrimitiveKind::Prototype: { RG_UNREACHABLE(); } break;
1388
+ }
1389
+
1390
+ #undef PUSH_INTEGER_SWAP
1391
+ #undef PUSH_INTEGER
1392
+
1393
+ return true;
1394
+ }
1395
+
1196
1396
  Napi::Function WrapFunction(Napi::Env env, const FunctionInfo *func)
1197
1397
  {
1198
1398
  InstanceData *instance = env.GetInstanceData<InstanceData>();
@@ -170,6 +170,9 @@ void DecodeBuffer(Span<uint8_t> buffer, const uint8_t *origin, const TypeInfo *r
170
170
  Napi::Value Decode(Napi::Value value, Size offset, const TypeInfo *type, const Size *len = nullptr);
171
171
  Napi::Value Decode(Napi::Env env, const uint8_t *ptr, const TypeInfo *type, const Size *len = nullptr);
172
172
 
173
+ bool Encode(Napi::Value ref, Size offset, Napi::Value value, const TypeInfo *type, const Size *len = nullptr);
174
+ bool Encode(Napi::Env env, uint8_t *ptr, Napi::Value value, const TypeInfo *type, const Size *len = nullptr);
175
+
173
176
  static inline Napi::Value NewBigInt(Napi::Env env, int64_t value)
174
177
  {
175
178
  if (value <= 9007199254740992ll && value >= -9007199254740992ll) {