koffi 0.9.28 → 0.9.31

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.31",
4
4
  "description": "Fast and simple FFI (foreign function interface) for Node.js",
5
5
  "keywords": [
6
6
  "foreign",
package/src/call_arm32.cc CHANGED
@@ -63,8 +63,9 @@ Napi::Function::Callback AnalyseFunction(InstanceData *, FunctionInfo *func)
63
63
  if (IsHFA(func->ret.type)) {
64
64
  func->ret.vec_count = func->ret.type->members.len *
65
65
  (func->ret.type->members[0].type->size / 4);
66
- } else if (func->ret.type->primitive != PrimitiveKind::Record) {
67
- func->ret.gpr_count = (func->ret.type->size >= 4) ? 2 : 1;
66
+ } else if (func->ret.type->primitive != PrimitiveKind::Record ||
67
+ func->ret.type->size <= 4) {
68
+ func->ret.gpr_count = (func->ret.type->size > 4) ? 2 : 1;
68
69
  } else {
69
70
  func->ret.use_memory = true;
70
71
  }
@@ -230,19 +231,21 @@ static Napi::Value TranslateCall(const Napi::CallbackInfo &info)
230
231
  uint32_t *gpr_ptr = nullptr;
231
232
  uint32_t *vec_ptr = nullptr;
232
233
 
233
- // Return through registers unless it's too big
234
+ // Unlike other call conventions, here we put the general-purpose
235
+ // registers just before the stack (so behind the vector ones).
236
+ // In the armv7hf calling convention, some arguments can end up
237
+ // partially in GPR, partially in the stack.
234
238
  if (RG_UNLIKELY(!call.AllocStack(func->args_size, 16, &args_ptr)))
235
239
  return env.Null();
236
- if (RG_UNLIKELY(!call.AllocStack(8 * 8, 8, &vec_ptr)))
237
- return env.Null();
238
240
  if (RG_UNLIKELY(!call.AllocStack(4 * 4, 8, &gpr_ptr)))
239
241
  return env.Null();
242
+ if (RG_UNLIKELY(!call.AllocStack(8 * 8, 8, &vec_ptr)))
243
+ return env.Null();
240
244
  if (func->ret.use_memory) {
241
245
  if (RG_UNLIKELY(!call.AllocHeap(func->ret.type->size, 16, &return_ptr)))
242
246
  return env.Null();
243
247
  *(uint8_t **)(gpr_ptr++) = return_ptr;
244
248
  }
245
- RG_ASSERT((uint8_t *)gpr_ptr + 16 == args_ptr);
246
249
 
247
250
  LocalArray<OutObject, MaxOutParameters> out_objects;
248
251
 
@@ -455,6 +458,8 @@ static Napi::Value TranslateCall(const Napi::CallbackInfo &info)
455
458
 
456
459
  case PrimitiveKind::Record: {
457
460
  if (func->ret.gpr_count) {
461
+ RG_ASSERT(func->ret.gpr_count <= 1);
462
+
458
463
  uint64_t ret = PERFORM_CALL(GG);
459
464
  uint32_t r0 = (uint32_t)ret;
460
465
 
@@ -469,8 +474,8 @@ static Napi::Value TranslateCall(const Napi::CallbackInfo &info)
469
474
  RG_ASSERT(return_ptr);
470
475
 
471
476
  uint64_t ret = PERFORM_CALL(GG);
472
- uint64_t r0 = (uint32_t)ret;
473
- RG_ASSERT(r0 == (uint64_t)return_ptr);
477
+ uint32_t r0 = (uint32_t)ret;
478
+ RG_ASSERT(r0 == (uint32_t)return_ptr);
474
479
 
475
480
  Napi::Object obj = PopObject(env, return_ptr, func->ret.type);
476
481
  return obj;
@@ -56,22 +56,22 @@
56
56
 
57
57
  // Prepare general purpose argument registers from array passed by caller.
58
58
  .macro forward_int
59
- ldr r3, [r1, 12]
60
- ldr r2, [r1, 8]
61
- ldr r0, [r1, 0]
62
- ldr r1, [r1, 4]
59
+ ldr r3, [r1, 76]
60
+ ldr r2, [r1, 72]
61
+ ldr r0, [r1, 64]
62
+ ldr r1, [r1, 68]
63
63
  .endm
64
64
 
65
65
  // Prepare vector argument registers from array passed by caller.
66
66
  .macro forward_vec
67
- vldr d7, [r1, 72]
68
- vldr d6, [r1, 64]
69
- vldr d5, [r1, 56]
70
- vldr d4, [r1, 48]
71
- vldr d3, [r1, 40]
72
- vldr d2, [r1, 32]
73
- vldr d1, [r1, 24]
74
- vldr d0, [r1, 16]
67
+ vldr d7, [r1, 56]
68
+ vldr d6, [r1, 48]
69
+ vldr d5, [r1, 40]
70
+ vldr d4, [r1, 32]
71
+ vldr d3, [r1, 24]
72
+ vldr d2, [r1, 16]
73
+ vldr d1, [r1, 8]
74
+ vldr d0, [r1, 0]
75
75
  .endm
76
76
 
77
77
  ForwardCallGG:
package/src/call_x86.cc CHANGED
@@ -31,21 +31,37 @@ extern "C" double ForwardCallRD(const void *func, uint8_t *sp);
31
31
 
32
32
  static Napi::Value TranslateCall(const Napi::CallbackInfo &info);
33
33
 
34
+ static inline bool IsRegular(Size size)
35
+ {
36
+ bool regular = (size <= 8 && !(size & (size - 1)));
37
+ return regular;
38
+ }
39
+
34
40
  Napi::Function::Callback AnalyseFunction(InstanceData *instance, FunctionInfo *func)
35
41
  {
36
- if (IsIntegral(func->ret.type->primitive)) {
37
- func->ret.trivial = true;
38
- } else if (func->ret.type->members.len == 1 && IsIntegral(func->ret.type->members[0].type->primitive)) {
42
+ int fast = (func->convention == CallConvention::Fastcall) ? 2 : 0;
43
+
44
+ if (func->ret.type->primitive != PrimitiveKind::Record) {
39
45
  func->ret.trivial = true;
40
46
  #ifdef _WIN32
41
- } else if (func->ret.type->members.len == 2 && IsIntegral(func->ret.type->members[0].type->primitive)
42
- && IsIntegral(func->ret.type->members[1].type->primitive)) {
43
- func->ret.trivial = true;
47
+ } else {
48
+ func->ret.trivial = IsRegular(func->ret.type->size);
44
49
  #endif
45
50
  }
51
+ #ifndef _WIN32
52
+ if (fast && !func->ret.trivial) {
53
+ func->ret.fast = true;
54
+ fast--;
55
+ }
56
+ #endif
46
57
 
47
58
  Size params_size = 0;
48
- for (const ParameterInfo &param: func->parameters) {
59
+ for (ParameterInfo &param: func->parameters) {
60
+ if (fast && param.type->size <= 4) {
61
+ param.fast = true;
62
+ fast--;
63
+ }
64
+
49
65
  params_size += std::max((int16_t)4, param.type->size);
50
66
  }
51
67
  func->args_size = params_size + 4 * !func->ret.trivial;
@@ -57,7 +73,7 @@ Napi::Function::Callback AnalyseFunction(InstanceData *instance, FunctionInfo *f
57
73
  } break;
58
74
  case CallConvention::Fastcall: {
59
75
  func->decorated_name = Fmt(&instance->str_alloc, "@%1@%2", func->name, params_size).ptr;
60
- func->args_size = std::max((Size)8, func->args_size);
76
+ func->args_size += 16;
61
77
  } break;
62
78
  }
63
79
 
@@ -80,14 +96,19 @@ static Napi::Value TranslateCall(const Napi::CallbackInfo &info)
80
96
 
81
97
  uint8_t *return_ptr = nullptr;
82
98
  uint32_t *args_ptr = nullptr;
99
+ uint32_t *fast_ptr = nullptr;
83
100
 
84
101
  // Pass return value in register or through memory
85
102
  if (RG_UNLIKELY(!call.AllocStack(func->args_size, 16, &args_ptr)))
86
103
  return env.Null();
104
+ if (func->convention == CallConvention::Fastcall) {
105
+ fast_ptr = args_ptr;
106
+ args_ptr += 4;
107
+ }
87
108
  if (!func->ret.trivial) {
88
109
  if (RG_UNLIKELY(!call.AllocHeap(func->ret.type->size, 16, &return_ptr)))
89
110
  return env.Null();
90
- *(args_ptr++) = (uint32_t)return_ptr;
111
+ *((func->ret.fast ? fast_ptr : args_ptr)++) = (uint32_t)return_ptr;
91
112
  }
92
113
 
93
114
  LocalArray<OutObject, MaxOutParameters> out_objects;
@@ -109,7 +130,7 @@ static Napi::Value TranslateCall(const Napi::CallbackInfo &info)
109
130
  }
110
131
 
111
132
  bool b = value.As<Napi::Boolean>();
112
- *(bool *)(args_ptr++) = b;
133
+ *(bool *)((param.fast ? fast_ptr : args_ptr)++) = b;
113
134
  } break;
114
135
  case PrimitiveKind::Int8:
115
136
  case PrimitiveKind::UInt8:
@@ -123,7 +144,7 @@ static Napi::Value TranslateCall(const Napi::CallbackInfo &info)
123
144
  }
124
145
 
125
146
  int32_t v = CopyNumber<int32_t>(value);
126
- *(args_ptr++) = (uint32_t)v;
147
+ *((param.fast ? fast_ptr : args_ptr)++) = (uint32_t)v;
127
148
  } break;
128
149
  case PrimitiveKind::Int64:
129
150
  case PrimitiveKind::UInt64: {
@@ -143,7 +164,7 @@ static Napi::Value TranslateCall(const Napi::CallbackInfo &info)
143
164
  }
144
165
 
145
166
  float f = CopyNumber<float>(value);
146
- *(float *)(args_ptr++) = f;
167
+ *(float *)((param.fast ? fast_ptr : args_ptr)++) = f;
147
168
  } break;
148
169
  case PrimitiveKind::Float64: {
149
170
  if (RG_UNLIKELY(!value.IsNumber() && !value.IsBigInt())) {
@@ -168,7 +189,7 @@ static Napi::Value TranslateCall(const Napi::CallbackInfo &info)
168
189
  return env.Null();
169
190
  }
170
191
 
171
- *(const char **)(args_ptr++) = str;
192
+ *(const char **)((param.fast ? fast_ptr : args_ptr)++) = str;
172
193
  } break;
173
194
  case PrimitiveKind::Pointer: {
174
195
  uint8_t *ptr;
@@ -194,7 +215,7 @@ static Napi::Value TranslateCall(const Napi::CallbackInfo &info)
194
215
  return env.Null();
195
216
  }
196
217
 
197
- *(uint8_t **)(args_ptr++) = ptr;
218
+ *(uint8_t **)((param.fast ? fast_ptr : args_ptr)++) = ptr;
198
219
  } break;
199
220
 
200
221
  case PrimitiveKind::Record: {
@@ -205,10 +226,16 @@ static Napi::Value TranslateCall(const Napi::CallbackInfo &info)
205
226
 
206
227
  Napi::Object obj = value.As<Napi::Object>();
207
228
 
208
- uint8_t *ptr = (uint8_t *)AlignUp(args_ptr, param.type->align);
209
- if (!call.PushObject(obj, param.type, ptr))
210
- return env.Null();
211
- args_ptr = (uint32_t *)AlignUp(ptr + param.type->size, 4);
229
+ if (param.fast) {
230
+ uint8_t *ptr = (uint8_t *)(fast_ptr++);
231
+ if (!call.PushObject(obj, param.type, ptr))
232
+ return env.Null();
233
+ } else {
234
+ uint8_t *ptr = (uint8_t *)AlignUp(args_ptr, param.type->align);
235
+ if (!call.PushObject(obj, param.type, ptr))
236
+ return env.Null();
237
+ args_ptr = (uint32_t *)AlignUp(ptr + param.type->size, 4);
238
+ }
212
239
  } break;
213
240
  }
214
241
  }
@@ -35,7 +35,7 @@
35
35
  .macro fastcall
36
36
  movl 0(%esp), %ecx
37
37
  movl 4(%esp), %edx
38
- add $8, %esp
38
+ addl $16, %esp
39
39
  .endm
40
40
 
41
41
  // Call native function.
@@ -34,7 +34,7 @@ endm
34
34
  fastcall macro
35
35
  mov ecx, dword ptr [esp+0]
36
36
  mov edx, dword ptr [esp+4]
37
- add esp, 8
37
+ add esp, 16
38
38
  endm
39
39
 
40
40
  ; Call native function.
package/src/ffi.cc CHANGED
@@ -75,20 +75,23 @@ 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
+
88
+ bool named = info.Length() > 1;
89
+
90
+ if (named && !info[0].IsString()) {
88
91
  ThrowError<Napi::TypeError>(env, "Unexpected %1 value for name, expected string", GetValueType(instance, info[0]));
89
92
  return env.Null();
90
93
  }
91
- if (!IsObject(info[1])) {
94
+ if (!IsObject(info[named])) {
92
95
  ThrowError<Napi::TypeError>(env, "Unexpected %1 value for members, expected object", GetValueType(instance, info[1]));
93
96
  return env.Null();
94
97
  }
@@ -96,8 +99,8 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info)
96
99
  TypeInfo *type = instance->types.AppendDefault();
97
100
  RG_DEFER_N(err_guard) { instance->types.RemoveLast(1); };
98
101
 
99
- std::string name = info[0].As<Napi::String>();
100
- Napi::Object obj = info[1].As<Napi::Object>();
102
+ std::string name = named ? info[0].As<Napi::String>() : std::string("<anonymous>");
103
+ Napi::Object obj = info[named].As<Napi::Object>();
101
104
  Napi::Array keys = obj.GetPropertyNames();
102
105
 
103
106
  type->name = DuplicateString(name.c_str(), &instance->str_alloc).ptr;
@@ -116,8 +119,10 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info)
116
119
  if (!member.type)
117
120
  return env.Null();
118
121
 
119
- type->size = (int16_t)(AlignLen(type->size, member.type->align) + member.type->size);
120
- type->align = std::max(type->align, member.type->align);
122
+ member.align = pad ? member.type->align : 1;
123
+
124
+ type->size = (int16_t)(AlignLen(type->size, member.align) + member.type->size);
125
+ type->align = std::max(type->align, member.align);
121
126
 
122
127
  type->members.Append(member);
123
128
  }
@@ -125,7 +130,7 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info)
125
130
  type->size = (int16_t)AlignLen(type->size, type->align);
126
131
 
127
132
  // If the insert succeeds, we cannot fail anymore
128
- if (!instance->types_map.TrySet(type).second) {
133
+ if (named && !instance->types_map.TrySet(type).second) {
129
134
  ThrowError<Napi::Error>(env, "Duplicate type name '%1'", type->name);
130
135
  return env.Null();
131
136
  }
@@ -137,6 +142,16 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info)
137
142
  return external;
138
143
  }
139
144
 
145
+ static Napi::Value CreatePaddedStructType(const Napi::CallbackInfo &info)
146
+ {
147
+ return CreateStructType(info, true);
148
+ }
149
+
150
+ static Napi::Value CreatePackedStructType(const Napi::CallbackInfo &info)
151
+ {
152
+ return CreateStructType(info, false);
153
+ }
154
+
140
155
  static Napi::Value CreateHandleType(const Napi::CallbackInfo &info)
141
156
  {
142
157
  Napi::Env env = info.Env();
@@ -159,7 +174,7 @@ static Napi::Value CreateHandleType(const Napi::CallbackInfo &info)
159
174
  type->name = DuplicateString(name.c_str(), &instance->str_alloc).ptr;
160
175
 
161
176
  type->primitive = PrimitiveKind::Record;
162
- type->align = RG_SIZE(void *);
177
+ type->align = alignof(void *);
163
178
  type->size = RG_SIZE(void *);
164
179
 
165
180
  // Add single handle member
@@ -169,6 +184,7 @@ static Napi::Value CreateHandleType(const Napi::CallbackInfo &info)
169
184
  member.name = "value";
170
185
  member.type = instance->types_map.FindValue("void *", nullptr);
171
186
  RG_ASSERT(member.type);
187
+ member.align = type->align;
172
188
 
173
189
  type->members.Append(member);
174
190
  }
@@ -552,19 +568,28 @@ FunctionInfo::~FunctionInfo()
552
568
  }
553
569
  }
554
570
 
555
- InstanceData::InstanceData()
571
+ static Span<uint8_t> AllocateAndAlign16(Allocator *alloc, Size size)
556
572
  {
557
- stack_mem.len = Mebibytes(2);
558
- stack_mem.ptr = (uint8_t *)Allocator::Allocate(&mem_alloc, stack_mem.len);
573
+ RG_ASSERT(AlignLen(size, 16) == size);
574
+
575
+ uint8_t *ptr = (uint8_t *)Allocator::Allocate(alloc, size);
576
+ uint8_t *aligned = AlignUp(ptr, 16);
577
+ Size delta = AlignLen(aligned - ptr, 16);
559
578
 
560
- heap_mem.len = Mebibytes(4);
561
- heap_mem.ptr = (uint8_t *)Allocator::Allocate(&mem_alloc, heap_mem.len);
579
+ return MakeSpan(aligned, size - delta);
580
+ }
581
+
582
+ InstanceData::InstanceData()
583
+ {
584
+ stack_mem = AllocateAndAlign16(&mem_alloc, Mebibytes(2));
585
+ heap_mem = AllocateAndAlign16(&mem_alloc, Mebibytes(4));
562
586
  }
563
587
 
564
588
  template <typename Func>
565
589
  static void SetExports(Napi::Env env, Func func)
566
590
  {
567
- func("struct", Napi::Function::New(env, CreateStructType));
591
+ func("struct", Napi::Function::New(env, CreatePaddedStructType));
592
+ func("pack", Napi::Function::New(env, CreatePackedStructType));
568
593
  func("handle", Napi::Function::New(env, CreateHandleType));
569
594
  func("pointer", Napi::Function::New(env, CreatePointerType));
570
595
  func("load", Napi::Function::New(env, LoadSharedLibrary));
package/src/ffi.hh CHANGED
@@ -63,12 +63,6 @@ static const char *const PrimitiveKindNames[] = {
63
63
  "Record"
64
64
  };
65
65
 
66
- static inline bool IsIntegral(PrimitiveKind primitive)
67
- {
68
- bool integral = ((int)primitive <= (int)PrimitiveKind::Pointer);
69
- return integral;
70
- }
71
-
72
66
  struct TypeInfo;
73
67
  struct RecordMember;
74
68
 
@@ -89,6 +83,7 @@ struct TypeInfo {
89
83
  struct RecordMember {
90
84
  const char *name;
91
85
  const TypeInfo *type;
86
+ int16_t align;
92
87
  };
93
88
 
94
89
  struct LibraryHolder {
@@ -107,6 +102,11 @@ enum class CallConvention {
107
102
  Stdcall,
108
103
  Fastcall
109
104
  };
105
+ static const char *const CallConventionNames[] = {
106
+ "Default",
107
+ "Stdcall",
108
+ "Fastcall"
109
+ };
110
110
 
111
111
  struct ParameterInfo {
112
112
  const TypeInfo *type;
@@ -127,6 +127,7 @@ struct ParameterInfo {
127
127
  int8_t vec_count;
128
128
  #elif defined(__i386__) || defined(_M_IX86)
129
129
  bool trivial; // Only matters for return value
130
+ bool fast;
130
131
  #endif
131
132
  };
132
133
 
package/src/util.cc CHANGED
@@ -81,36 +81,34 @@ 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
+ len++;
106
+
107
+ buf.ptr = (char *)Allocator::Allocate(&big_alloc, (Size)len);
108
+ buf.len = (Size)len;
109
+
110
+ status = napi_get_value_string_utf8(env, value, buf.ptr, (size_t)buf.len, &len);
111
+ RG_ASSERT(status == napi_ok);
114
112
  }
115
113
 
116
114
  return buf.ptr;
@@ -132,7 +130,7 @@ bool CallData::PushObject(const Napi::Object &obj, const TypeInfo *type, uint8_t
132
130
  return false;
133
131
  }
134
132
 
135
- dest = AlignUp(dest, member.type->align);
133
+ dest = AlignUp(dest, member.align);
136
134
 
137
135
  switch (member.type->primitive) {
138
136
  case PrimitiveKind::Void: { RG_UNREACHABLE(); } break;
@@ -229,7 +227,7 @@ void PopObject(Napi::Object obj, const uint8_t *ptr, const TypeInfo *type)
229
227
  RG_ASSERT(type->primitive == PrimitiveKind::Record);
230
228
 
231
229
  for (const RecordMember &member: type->members) {
232
- ptr = AlignUp(ptr, member.type->align);
230
+ ptr = AlignUp(ptr, member.align);
233
231
 
234
232
  switch (member.type->primitive) {
235
233
  case PrimitiveKind::Void: { RG_UNREACHABLE(); } break;
@@ -329,7 +327,7 @@ static void DumpMemory(const char *type, Span<const uint8_t> bytes)
329
327
 
330
328
  void CallData::DumpDebug() const
331
329
  {
332
- PrintLn(stderr, "%!..+---- %1 ----%!0", func->name);
330
+ PrintLn(stderr, "%!..+---- %1 (%2) ----%!0", func->name, CallConventionNames[(int)func->convention]);
333
331
 
334
332
  if (func->parameters.len) {
335
333
  PrintLn(stderr, "Parameters:");
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/test.js CHANGED
@@ -276,7 +276,7 @@ async function test() {
276
276
  basename !== 'node_modules' &&
277
277
  basename !== 'node' &&
278
278
  basename !== 'build' &&
279
- basename !== 'luigi';
279
+ basename !== 'luiggi';
280
280
  });
281
281
 
282
282
  success &= await start(false);
package/test/tests/misc.c CHANGED
@@ -11,8 +11,10 @@
11
11
  // You should have received a copy of the GNU Affero General Public License
12
12
  // along with this program. If not, see https://www.gnu.org/licenses/.
13
13
 
14
+ #include <stdlib.h>
14
15
  #include <stdio.h>
15
16
  #include <inttypes.h>
17
+ #include <string.h>
16
18
 
17
19
  #ifdef _WIN32
18
20
  #define EXPORT __declspec(dllexport)
@@ -32,6 +34,13 @@
32
34
  #define STDCALL
33
35
  #endif
34
36
 
37
+ typedef struct Pack1 {
38
+ int a;
39
+ } Pack1;
40
+ typedef struct Pack2 {
41
+ int a;
42
+ int b;
43
+ } Pack2;
35
44
  typedef struct Pack3 {
36
45
  int a;
37
46
  int b;
@@ -53,6 +62,60 @@ typedef struct BFG {
53
62
  double g;
54
63
  } inner;
55
64
  } BFG;
65
+ #pragma pack(push, 1)
66
+ typedef struct PackedBFG {
67
+ int8_t a;
68
+ int64_t b;
69
+ signed char c;
70
+ const char *d;
71
+ short e;
72
+ struct {
73
+ float f;
74
+ double g;
75
+ } inner;
76
+ } PackedBFG;
77
+ #pragma pack(pop)
78
+
79
+ EXPORT void FillPack1(int a, Pack1 *p)
80
+ {
81
+ p->a = a;
82
+ }
83
+
84
+ EXPORT Pack1 RetPack1(int a)
85
+ {
86
+ Pack1 p;
87
+
88
+ p.a = a;
89
+
90
+ return p;
91
+ }
92
+
93
+ EXPORT void FASTCALL AddPack1(int a, Pack1 *p)
94
+ {
95
+ p->a += a;
96
+ }
97
+
98
+ EXPORT void FillPack2(int a, int b, Pack2 *p)
99
+ {
100
+ p->a = a;
101
+ p->b = b;
102
+ }
103
+
104
+ EXPORT Pack2 RetPack2(int a, int b)
105
+ {
106
+ Pack2 p;
107
+
108
+ p.a = a;
109
+ p.b = b;
110
+
111
+ return p;
112
+ }
113
+
114
+ EXPORT void FASTCALL AddPack2(int a, int b, Pack2 *p)
115
+ {
116
+ p->a += a;
117
+ p->b += b;
118
+ }
56
119
 
57
120
  EXPORT void FillPack3(int a, int b, int c, Pack3 *p)
58
121
  {
@@ -64,7 +127,7 @@ EXPORT void FillPack3(int a, int b, int c, Pack3 *p)
64
127
  EXPORT Pack3 RetPack3(int a, int b, int c)
65
128
  {
66
129
  Pack3 p;
67
-
130
+
68
131
  p.a = a;
69
132
  p.b = b;
70
133
  p.c = c;
@@ -135,14 +198,17 @@ EXPORT const char *ConcatenateToStr8(int64_t a, int64_t b, int64_t c, int64_t d,
135
198
  return buf;
136
199
  }
137
200
 
138
- EXPORT BFG STDCALL MakeBFG(int x, double y, BFG *p)
201
+ EXPORT BFG STDCALL MakeBFG(BFG *p, int x, double y, const char *str)
139
202
  {
140
203
  BFG bfg;
141
204
 
205
+ char buf[64];
206
+ snprintf(buf, sizeof(buf), "X/%s/X", str);
207
+
142
208
  bfg.a = x;
143
209
  bfg.b = x * 2;
144
210
  bfg.c = x - 27;
145
- bfg.d = "Hello";
211
+ bfg.d = buf;
146
212
  bfg.e = x * 27;
147
213
  bfg.inner.f = (float)y * x;
148
214
  bfg.inner.g = (double)y - x;
@@ -150,3 +216,32 @@ EXPORT BFG STDCALL MakeBFG(int x, double y, BFG *p)
150
216
 
151
217
  return bfg;
152
218
  }
219
+
220
+ EXPORT PackedBFG FASTCALL MakePackedBFG(int x, double y, PackedBFG *p, const char *str)
221
+ {
222
+ PackedBFG bfg;
223
+
224
+ char buf[64];
225
+ snprintf(buf, sizeof(buf), "X/%s/X", str);
226
+
227
+ bfg.a = x;
228
+ bfg.b = x * 2;
229
+ bfg.c = x - 27;
230
+ bfg.d = buf;
231
+ bfg.e = x * 27;
232
+ bfg.inner.f = (float)y * x;
233
+ bfg.inner.g = (double)y - x;
234
+ *p = bfg;
235
+
236
+ return bfg;
237
+ }
238
+
239
+ EXPORT const char *ReturnBigString(const char *str)
240
+ {
241
+ static char buf[16 * 1024 * 1024];
242
+
243
+ size_t len = strlen(str);
244
+ memcpy(buf, str, len + 1);
245
+
246
+ return buf;
247
+ }
@@ -17,6 +17,13 @@ const koffi = require('../build/koffi.node');
17
17
  const assert = require('assert');
18
18
  const path = require('path');
19
19
 
20
+ const Pack1 = koffi.struct('Pack1', {
21
+ a: 'int'
22
+ });
23
+ const Pack2 = koffi.struct('Pack2', {
24
+ a: 'int',
25
+ b: 'int'
26
+ });
20
27
  const Pack3 = koffi.struct('Pack3', {
21
28
  a: 'int',
22
29
  b: 'int',
@@ -29,7 +36,18 @@ const BFG = koffi.struct('BFG', {
29
36
  c: 'char',
30
37
  d: 'string',
31
38
  e: 'short',
32
- inner: koffi.struct('BFG.inner', {
39
+ inner: koffi.struct({
40
+ f: 'float',
41
+ g: 'double'
42
+ })
43
+ });
44
+ const PackedBFG = koffi.pack('PackedBFG', {
45
+ a: 'int8_t',
46
+ b: 'int64_t',
47
+ c: 'char',
48
+ d: 'string',
49
+ e: 'short',
50
+ inner: koffi.pack({
33
51
  f: 'float',
34
52
  g: 'double'
35
53
  })
@@ -51,6 +69,12 @@ async function test() {
51
69
  let lib_filename = path.dirname(__filename) + '/../build/misc' + koffi.extension;
52
70
  let lib = koffi.load(lib_filename);
53
71
 
72
+ const FillPack1 = lib.cdecl('FillPack1', 'void', ['int', koffi.out(koffi.pointer(Pack1))]);
73
+ const RetPack1 = lib.cdecl('RetPack1', Pack1, ['int']);
74
+ const AddPack1 = lib.fastcall('AddPack1', 'void', ['int', koffi.inout(koffi.pointer(Pack1))]);
75
+ const FillPack2 = lib.cdecl('FillPack2', 'void', ['int', 'int', koffi.out(koffi.pointer(Pack2))]);
76
+ const RetPack2 = lib.cdecl('RetPack2', Pack2, ['int', 'int']);
77
+ const AddPack2 = lib.fastcall('AddPack2', 'void', ['int', 'int', koffi.inout(koffi.pointer(Pack2))]);
54
78
  const FillPack3 = lib.cdecl('FillPack3', 'void', ['int', 'int', 'int', koffi.out(koffi.pointer(Pack3))]);
55
79
  const RetPack3 = lib.cdecl('RetPack3', Pack3, ['int', 'int', 'int']);
56
80
  const AddPack3 = lib.fastcall('AddPack3', 'void', ['int', 'int', 'int', koffi.inout(koffi.pointer(Pack3))]);
@@ -60,28 +84,81 @@ async function test() {
60
84
  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
85
  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
86
  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))]);
87
+ const MakeBFG = lib.stdcall('MakeBFG', BFG, [koffi.out(koffi.pointer(BFG)), 'int', 'double', 'string']);
88
+ const MakePackedBFG = lib.fastcall('MakePackedBFG', PackedBFG, ['int', 'double', koffi.out(koffi.pointer(PackedBFG)), 'string']);
89
+ const ReturnBigString = lib.stdcall('ReturnBigString', 'string', ['string']);
64
90
 
65
- let p = {};
91
+ // Simple tests with Pack1
92
+ {
93
+ let p = {};
66
94
 
67
- FillPack3(1, 2, 3, p);
68
- assert.deepEqual(p, { a: 1, b: 2, c: 3 });
95
+ FillPack1(777, p);
96
+ assert.deepEqual(p, { a: 777 });
97
+
98
+ let q = RetPack1(6);
99
+ assert.deepEqual(q, { a: 6 });
100
+
101
+ AddPack1(6, p);
102
+ assert.deepEqual(p, { a: 783 });
103
+ }
69
104
 
70
- let q = RetPack3(6, 9, -12);
71
- assert.deepEqual(q, { a: 6, b: 9, c: -12 });
105
+ // Simple tests with Pack2
106
+ {
107
+ let p = {};
72
108
 
73
- AddPack3(6, 9, -12, p);
74
- assert.deepEqual(p, { a: 7, b: 11, c: -9 });
109
+ FillPack2(123, 456, p);
110
+ assert.deepEqual(p, { a: 123, b: 456 });
75
111
 
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');
112
+ let q = RetPack2(6, 9);
113
+ assert.deepEqual(q, { a: 6, b: 9 });
82
114
 
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);
115
+ AddPack2(6, 9, p);
116
+ assert.deepEqual(p, { a: 129, b: 465 });
117
+ }
118
+
119
+ // Simple tests with Pack3
120
+ {
121
+ let p = {};
122
+
123
+ FillPack3(1, 2, 3, p);
124
+ assert.deepEqual(p, { a: 1, b: 2, c: 3 });
125
+
126
+ let q = RetPack3(6, 9, -12);
127
+ assert.deepEqual(q, { a: 6, b: 9, c: -12 });
128
+
129
+ AddPack3(6, 9, -12, p);
130
+ assert.deepEqual(p, { a: 7, b: 11, c: -9 });
131
+ }
132
+
133
+ // Many parameters
134
+ {
135
+ assert.equal(ConcatenateToInt1(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
136
+ assert.equal(ConcatenateToInt4(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
137
+ assert.equal(ConcatenateToInt8(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
138
+ assert.equal(ConcatenateToStr1(5, 6, 1, 2, 3, 9, 4, 4, {i: 0, j: 6, k: 8}, 7), '561239440687');
139
+ assert.equal(ConcatenateToStr4(5, 6, 1, 2, 3, 9, 4, 4, {i: 0, j: 6, k: 8}, 7), '561239440687');
140
+ assert.equal(ConcatenateToStr8(5, 6, 1, 2, 3, 9, 4, 4, {i: 0, j: 6, k: 8}, 7), '561239440687');
141
+ }
142
+
143
+ // Big struct
144
+ {
145
+ let out = {};
146
+ let bfg = MakeBFG(out, 2, 7, '__Hello123456789++++foobarFOOBAR!__');
147
+ assert.deepEqual(bfg, { a: 2, b: 4, c: -25, d: 'X/__Hello123456789++++foobarFOOBAR!__/X', e: 54, inner: { f: 14, g: 5 } });
148
+ assert.deepEqual(out, bfg);
149
+ }
150
+
151
+ // Packed struct
152
+ {
153
+ let out = {};
154
+ let bfg = MakePackedBFG(2, 7, out, '__Hello123456789++++foobarFOOBAR!__');
155
+ assert.deepEqual(bfg, { a: 2, b: 4, c: -25, d: 'X/__Hello123456789++++foobarFOOBAR!__/X', e: 54, inner: { f: 14, g: 5 } });
156
+ assert.deepEqual(out, bfg);
157
+ }
158
+
159
+ // Big string
160
+ {
161
+ let str = 'fooBAR!'.repeat(1024 * 1024);
162
+ assert.equal(ReturnBigString(str), str);
163
+ }
87
164
  }