koffi 0.9.28 → 0.9.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "0.9.28",
3
+ "version": "0.9.29",
4
4
  "description": "Fast and simple FFI (foreign function interface) for Node.js",
5
5
  "keywords": [
6
6
  "foreign",
package/src/ffi.cc CHANGED
@@ -75,20 +75,20 @@ static const TypeInfo *ResolveType(const InstanceData *instance, Napi::Value val
75
75
  }
76
76
  }
77
77
 
78
- static Napi::Value CreateStructType(const Napi::CallbackInfo &info)
78
+ static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
79
79
  {
80
80
  Napi::Env env = info.Env();
81
81
  InstanceData *instance = env.GetInstanceData<InstanceData>();
82
82
 
83
- if (info.Length() < 2) {
84
- ThrowError<Napi::TypeError>(env, "Expected 2 arguments, got %1", info.Length());
83
+ if (info.Length() < 1) {
84
+ ThrowError<Napi::TypeError>(env, "Expected 1 or 2 arguments, got %1", info.Length());
85
85
  return env.Null();
86
86
  }
87
- if (!info[0].IsString()) {
87
+ if (info.Length() > 1 && !info[0].IsString()) {
88
88
  ThrowError<Napi::TypeError>(env, "Unexpected %1 value for name, expected string", GetValueType(instance, info[0]));
89
89
  return env.Null();
90
90
  }
91
- if (!IsObject(info[1])) {
91
+ if (!IsObject(info[info.Length() > 1])) {
92
92
  ThrowError<Napi::TypeError>(env, "Unexpected %1 value for members, expected object", GetValueType(instance, info[1]));
93
93
  return env.Null();
94
94
  }
@@ -96,8 +96,8 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info)
96
96
  TypeInfo *type = instance->types.AppendDefault();
97
97
  RG_DEFER_N(err_guard) { instance->types.RemoveLast(1); };
98
98
 
99
- std::string name = info[0].As<Napi::String>();
100
- Napi::Object obj = info[1].As<Napi::Object>();
99
+ std::string name = info.Length() > 1 ? info[0].As<Napi::String>() : std::string("<anonymous>");
100
+ Napi::Object obj = info[info.Length() > 1].As<Napi::Object>();
101
101
  Napi::Array keys = obj.GetPropertyNames();
102
102
 
103
103
  type->name = DuplicateString(name.c_str(), &instance->str_alloc).ptr;
@@ -116,8 +116,10 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info)
116
116
  if (!member.type)
117
117
  return env.Null();
118
118
 
119
- type->size = (int16_t)(AlignLen(type->size, member.type->align) + member.type->size);
120
- type->align = std::max(type->align, member.type->align);
119
+ member.align = pad ? member.type->align : 1;
120
+
121
+ type->size = (int16_t)(AlignLen(type->size, member.align) + member.type->size);
122
+ type->align = std::max(type->align, member.align);
121
123
 
122
124
  type->members.Append(member);
123
125
  }
@@ -125,7 +127,7 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info)
125
127
  type->size = (int16_t)AlignLen(type->size, type->align);
126
128
 
127
129
  // If the insert succeeds, we cannot fail anymore
128
- if (!instance->types_map.TrySet(type).second) {
130
+ if (info.Length() > 1 && !instance->types_map.TrySet(type).second) {
129
131
  ThrowError<Napi::Error>(env, "Duplicate type name '%1'", type->name);
130
132
  return env.Null();
131
133
  }
@@ -137,6 +139,16 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info)
137
139
  return external;
138
140
  }
139
141
 
142
+ static Napi::Value CreatePaddedStructType(const Napi::CallbackInfo &info)
143
+ {
144
+ return CreateStructType(info, true);
145
+ }
146
+
147
+ static Napi::Value CreatePackedStructType(const Napi::CallbackInfo &info)
148
+ {
149
+ return CreateStructType(info, false);
150
+ }
151
+
140
152
  static Napi::Value CreateHandleType(const Napi::CallbackInfo &info)
141
153
  {
142
154
  Napi::Env env = info.Env();
@@ -564,7 +576,8 @@ InstanceData::InstanceData()
564
576
  template <typename Func>
565
577
  static void SetExports(Napi::Env env, Func func)
566
578
  {
567
- func("struct", Napi::Function::New(env, CreateStructType));
579
+ func("struct", Napi::Function::New(env, CreatePaddedStructType));
580
+ func("pack", Napi::Function::New(env, CreatePackedStructType));
568
581
  func("handle", Napi::Function::New(env, CreateHandleType));
569
582
  func("pointer", Napi::Function::New(env, CreatePointerType));
570
583
  func("load", Napi::Function::New(env, LoadSharedLibrary));
package/src/ffi.hh CHANGED
@@ -89,6 +89,7 @@ struct TypeInfo {
89
89
  struct RecordMember {
90
90
  const char *name;
91
91
  const TypeInfo *type;
92
+ int16_t align;
92
93
  };
93
94
 
94
95
  struct LibraryHolder {
package/src/util.cc CHANGED
@@ -81,36 +81,32 @@ const char *CallData::PushString(const Napi::Value &value)
81
81
  {
82
82
  RG_ASSERT(value.IsString());
83
83
 
84
- const Size SmallSize = 32;
85
-
86
84
  Napi::Env env = value.Env();
87
- napi_status status;
88
85
 
89
86
  Span<char> buf;
90
87
  size_t len = 0;
88
+ napi_status status;
91
89
 
92
- // Optimize for small strings
93
- buf.len = SmallSize;
94
- if (RG_UNLIKELY(!AllocHeap(buf.len, 1, &buf.ptr)))
95
- return nullptr;
96
- if (RG_UNLIKELY(napi_get_value_string_utf8(env, value, buf.ptr, (size_t)buf.len, &len) != napi_ok)) {
97
- ThrowError<Napi::Error>(env, "Failed to convert string to UTF-8");
98
- return nullptr;
99
- }
90
+ buf.ptr = (char *)heap_mem->ptr;
91
+ buf.len = std::max((Size)0, heap_mem->len - Kibibytes(32));
92
+
93
+ status = napi_get_value_string_utf8(env, value, buf.ptr, (size_t)buf.len, &len);
94
+ RG_ASSERT(status == napi_ok);
100
95
 
101
- // Slow path for bigger strings
102
- if (len >= SmallSize - 1) {
96
+ len++;
97
+
98
+ if (RG_LIKELY(len < (size_t)buf.len)) {
99
+ heap_mem->ptr += (Size)len;
100
+ heap_mem->len -= (Size)len;
101
+ } else {
103
102
  status = napi_get_value_string_utf8(env, value, nullptr, 0, &len);
104
103
  RG_ASSERT(status == napi_ok);
105
- RG_ASSERT(len >= SmallSize);
106
-
107
- buf.len = (Size)len + 1;
108
- if (RG_UNLIKELY(!AllocHeap(buf.len - SmallSize, 1)))
109
- return nullptr;
110
- if (RG_UNLIKELY(napi_get_value_string_utf8(env, value, buf.ptr, (size_t)buf.len, &len) != napi_ok)) {
111
- ThrowError<Napi::Error>(env, "Failed to convert string to UTF-8");
112
- return nullptr;
113
- }
104
+
105
+ buf.ptr = (char *)Allocator::Allocate(&big_alloc, (Size)len);
106
+ buf.len = (Size)len;
107
+
108
+ status = napi_get_value_string_utf8(env, value, buf.ptr, (size_t)buf.len, &len);
109
+ RG_ASSERT(status == napi_ok);
114
110
  }
115
111
 
116
112
  return buf.ptr;
@@ -132,7 +128,7 @@ bool CallData::PushObject(const Napi::Object &obj, const TypeInfo *type, uint8_t
132
128
  return false;
133
129
  }
134
130
 
135
- dest = AlignUp(dest, member.type->align);
131
+ dest = AlignUp(dest, member.align);
136
132
 
137
133
  switch (member.type->primitive) {
138
134
  case PrimitiveKind::Void: { RG_UNREACHABLE(); } break;
@@ -229,7 +225,7 @@ void PopObject(Napi::Object obj, const uint8_t *ptr, const TypeInfo *type)
229
225
  RG_ASSERT(type->primitive == PrimitiveKind::Record);
230
226
 
231
227
  for (const RecordMember &member: type->members) {
232
- ptr = AlignUp(ptr, member.type->align);
228
+ ptr = AlignUp(ptr, member.align);
233
229
 
234
230
  switch (member.type->primitive) {
235
231
  case PrimitiveKind::Void: { RG_UNREACHABLE(); } break;
package/src/util.hh CHANGED
@@ -74,6 +74,7 @@ class CallData {
74
74
 
75
75
  Span<uint8_t> *stack_mem;
76
76
  Span<uint8_t> *heap_mem;
77
+ BlockAllocator big_alloc;
77
78
 
78
79
  Span<uint8_t> old_stack_mem;
79
80
  Span<uint8_t> old_heap_mem;
package/test/tests/misc.c CHANGED
@@ -53,6 +53,19 @@ typedef struct BFG {
53
53
  double g;
54
54
  } inner;
55
55
  } BFG;
56
+ #pragma pack(push, 1)
57
+ typedef struct PackedBFG {
58
+ int8_t a;
59
+ int64_t b;
60
+ signed char c;
61
+ const char *d;
62
+ short e;
63
+ struct {
64
+ float f;
65
+ double g;
66
+ } inner;
67
+ } PackedBFG;
68
+ #pragma pack(pop)
56
69
 
57
70
  EXPORT void FillPack3(int a, int b, int c, Pack3 *p)
58
71
  {
@@ -135,14 +148,36 @@ EXPORT const char *ConcatenateToStr8(int64_t a, int64_t b, int64_t c, int64_t d,
135
148
  return buf;
136
149
  }
137
150
 
138
- EXPORT BFG STDCALL MakeBFG(int x, double y, BFG *p)
151
+ EXPORT BFG STDCALL MakeBFG(BFG *p, int x, double y, const char *str)
139
152
  {
140
153
  BFG bfg;
141
154
 
155
+ char buf[64];
156
+ snprintf(buf, sizeof(buf), "X/%s/X", str);
157
+
158
+ bfg.a = x;
159
+ bfg.b = x * 2;
160
+ bfg.c = x - 27;
161
+ bfg.d = buf;
162
+ bfg.e = x * 27;
163
+ bfg.inner.f = (float)y * x;
164
+ bfg.inner.g = (double)y - x;
165
+ *p = bfg;
166
+
167
+ return bfg;
168
+ }
169
+
170
+ EXPORT PackedBFG FASTCALL MakePackedBFG(int x, double y, PackedBFG *p, const char *str)
171
+ {
172
+ PackedBFG bfg;
173
+
174
+ char buf[64];
175
+ snprintf(buf, sizeof(buf), "X/%s/X", str);
176
+
142
177
  bfg.a = x;
143
178
  bfg.b = x * 2;
144
179
  bfg.c = x - 27;
145
- bfg.d = "Hello";
180
+ bfg.d = buf;
146
181
  bfg.e = x * 27;
147
182
  bfg.inner.f = (float)y * x;
148
183
  bfg.inner.g = (double)y - x;
@@ -150,3 +185,13 @@ EXPORT BFG STDCALL MakeBFG(int x, double y, BFG *p)
150
185
 
151
186
  return bfg;
152
187
  }
188
+
189
+ EXPORT const char *ReturnBigString(const char *str)
190
+ {
191
+ static char buf[1 * 1024 * 1024];
192
+
193
+ size_t len = strlen(str);
194
+ memcpy(buf, str, len + 1);
195
+
196
+ return buf;
197
+ }
@@ -29,7 +29,18 @@ const BFG = koffi.struct('BFG', {
29
29
  c: 'char',
30
30
  d: 'string',
31
31
  e: 'short',
32
- inner: koffi.struct('BFG.inner', {
32
+ inner: koffi.struct({
33
+ f: 'float',
34
+ g: 'double'
35
+ })
36
+ });
37
+ const PackedBFG = koffi.pack('PackedBFG', {
38
+ a: 'int8_t',
39
+ b: 'int64_t',
40
+ c: 'char',
41
+ d: 'string',
42
+ e: 'short',
43
+ inner: koffi.pack({
33
44
  f: 'float',
34
45
  g: 'double'
35
46
  })
@@ -60,28 +71,53 @@ async function test() {
60
71
  const ConcatenateToStr1 = lib.cdecl('ConcatenateToStr1', 'string', [...Array(8).fill('int8_t'), koffi.struct('IJK1', {i: 'int8_t', j: 'int8_t', k: 'int8_t'}), 'int8_t']);
61
72
  const ConcatenateToStr4 = lib.cdecl('ConcatenateToStr4', 'string', [...Array(8).fill('int32_t'), koffi.pointer(koffi.struct('IJK4', {i: 'int32_t', j: 'int32_t', k: 'int32_t'})), 'int32_t']);
62
73
  const ConcatenateToStr8 = lib.cdecl('ConcatenateToStr8', 'string', [...Array(8).fill('int64_t'), koffi.struct('IJK8', {i: 'int64_t', j: 'int64_t', k: 'int64_t'}), 'int64_t']);
63
- const MakeBFG = lib.stdcall('MakeBFG', BFG, ['int', 'double', koffi.out(koffi.pointer(BFG))]);
74
+ const MakeBFG = lib.stdcall('MakeBFG', BFG, [koffi.out(koffi.pointer(BFG)), 'int', 'double', 'string']);
75
+ const MakePackedBFG = lib.stdcall('MakePackedBFG', PackedBFG, ['int', 'double', koffi.out(koffi.pointer(PackedBFG)), 'string']);
76
+ const ReturnBigString = lib.stdcall('ReturnBigString', 'string', ['string']);
77
+
78
+ // Simple tests
79
+ {
80
+ let p = {};
64
81
 
65
- let p = {};
82
+ FillPack3(1, 2, 3, p);
83
+ assert.deepEqual(p, { a: 1, b: 2, c: 3 });
66
84
 
67
- FillPack3(1, 2, 3, p);
68
- assert.deepEqual(p, { a: 1, b: 2, c: 3 });
85
+ let q = RetPack3(6, 9, -12);
86
+ assert.deepEqual(q, { a: 6, b: 9, c: -12 });
87
+
88
+ AddPack3(6, 9, -12, p);
89
+ assert.deepEqual(p, { a: 7, b: 11, c: -9 });
90
+ }
69
91
 
70
- let q = RetPack3(6, 9, -12);
71
- assert.deepEqual(q, { a: 6, b: 9, c: -12 });
92
+ // Many parameters
93
+ {
94
+ assert.equal(ConcatenateToInt1(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
95
+ assert.equal(ConcatenateToInt4(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
96
+ assert.equal(ConcatenateToInt8(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
97
+ assert.equal(ConcatenateToStr1(5, 6, 1, 2, 3, 9, 4, 4, {i: 0, j: 6, k: 8}, 7), '561239440687');
98
+ assert.equal(ConcatenateToStr4(5, 6, 1, 2, 3, 9, 4, 4, {i: 0, j: 6, k: 8}, 7), '561239440687');
99
+ assert.equal(ConcatenateToStr8(5, 6, 1, 2, 3, 9, 4, 4, {i: 0, j: 6, k: 8}, 7), '561239440687');
100
+ }
72
101
 
73
- AddPack3(6, 9, -12, p);
74
- assert.deepEqual(p, { a: 7, b: 11, c: -9 });
102
+ // Big struct
103
+ {
104
+ let out = {};
105
+ let bfg = MakeBFG(out, 2, 7, '__Hello123456789++++foobarFOOBAR!__');
106
+ assert.deepEqual(bfg, { a: 2, b: 4, c: -25, d: 'X/__Hello123456789++++foobarFOOBAR!__/X', e: 54, inner: { f: 14, g: 5 } });
107
+ assert.deepEqual(out, bfg);
108
+ }
75
109
 
76
- assert.equal(ConcatenateToInt1(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
77
- assert.equal(ConcatenateToInt4(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
78
- assert.equal(ConcatenateToInt8(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
79
- assert.equal(ConcatenateToStr1(5, 6, 1, 2, 3, 9, 4, 4, {i: 0, j: 6, k: 8}, 7), '561239440687');
80
- assert.equal(ConcatenateToStr4(5, 6, 1, 2, 3, 9, 4, 4, {i: 0, j: 6, k: 8}, 7), '561239440687');
81
- assert.equal(ConcatenateToStr8(5, 6, 1, 2, 3, 9, 4, 4, {i: 0, j: 6, k: 8}, 7), '561239440687');
110
+ // Packed struct
111
+ {
112
+ let out = {};
113
+ let bfg = MakePackedBFG(2, 7, out, '__Hello123456789++++foobarFOOBAR!__');
114
+ assert.deepEqual(bfg, { a: 2, b: 4, c: -25, d: 'X/__Hello123456789++++foobarFOOBAR!__/X', e: 54, inner: { f: 14, g: 5 } });
115
+ assert.deepEqual(out, bfg);
116
+ }
82
117
 
83
- let out = {};
84
- let bfg = MakeBFG(2, 7, out);
85
- assert.deepEqual(bfg, { a: 2, b: 4, c: -25, d: 'Hello', e: 54, inner: { f: 14, g: 5 } });
86
- assert.deepEqual(out, bfg);
118
+ // Big string
119
+ {
120
+ let str = 'fooBAR!'.repeat(1024 * 1024);
121
+ assert.equal(ReturnBigString(str), str);
122
+ }
87
123
  }