/
FFIExternalType.class.st
395 lines (329 loc) · 11.3 KB
/
FFIExternalType.class.st
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
"
I'm an abstract class to implement FFI external types (types who will later be mapped to something understandable for a C library)
"
Class {
#name : #FFIExternalType,
#superclass : #Object,
#instVars : [
'pointerArity',
'loader'
],
#category : #'UnifiedFFI-Types'
}
{ #category : #converting }
FFIExternalType class >> asExternalTypeOn: generator [
^ self new
]
{ #category : #accessing }
FFIExternalType class >> externalType [
^ self subclassResponsibility
]
{ #category : #accessing }
FFIExternalType class >> externalTypeAlignment [
"Answer a number of bytes, which takes a value of given type
(not a pointer to it)"
self subclassResponsibility
]
{ #category : #accessing }
FFIExternalType class >> externalTypeSize [
"Answer a number of bytes, which takes a value of given type
(not a pointer to it)"
self subclassResponsibility
]
{ #category : #private }
FFIExternalType class >> naturalPointerArity [
"Indicates 'natural' pointer artity of type (atomic types are zero while any kind of
pointer/reference/etc. is one)"
^ 0
]
{ #category : #'instance creation' }
FFIExternalType class >> newBuffer [
"This method is used to provide convencience buffer accessor to types.
This is useful when you need to pass a reference to an argument to take the value later.
For example:
void getDimensions(double *width, double *height) {
*width = 2.0;
*height = 3.0;
}
int main() {
double w, h;
get_dimensions( &w, &h);
printf(""%f %f"", w, h)
}
==> 2.0 3.0
would have an FFI declaration...
ffi_getDimensions__width: width height: height
^ #( (void getDimensions( double *width, double *height) )
testMain
| widthBuffer heightBuffer width height |
widthBuffer := FFIFloat64 newBuffer.
heightBuffer := FFIFloat64 newBuffer.
ffi_getDimensions__width: widthBuffer height: heightBuffer.
width := widthBuffer doubleAt: 1.
height := heightBuffer doubleAt: 1.
Transcript crShow: width; tab; show: height.
==> 2.0 3.0
"
^ ByteArray new: self externalTypeSize
]
{ #category : #accessing }
FFIExternalType class >> pointerAlignment [
^ OSPlatform current ffiPointerAlignment
]
{ #category : #accessing }
FFIExternalType class >> pointerSize [
"Answer a number of bytes, which takes a pointer value"
^ Smalltalk vm wordSize
]
{ #category : #public }
FFIExternalType class >> resolveType: aTypeName [
^ FFICallout new resolveType: aTypeName
]
{ #category : #public }
FFIExternalType class >> sizeOf: aTypeName [
^ (self resolveType: aTypeName) typeSize
]
{ #category : #'emitting code' }
FFIExternalType >> basicEmitArgument: aBuilder context: aContext inCallout: aCallout [
self loader
emitArgument: aBuilder
context: aContext
]
{ #category : #private }
FFIExternalType >> basicHandle: aHandle at: index [
self subclassResponsibility
]
{ #category : #private }
FFIExternalType >> basicHandle: aHandle at: index put: value [
self subclassResponsibility
]
{ #category : #callbacks }
FFIExternalType >> callbackReturnOn: callbackContext for: anObject [
"By default, I answer an integral return (not a float)"
^ callbackContext wordResult: anObject
]
{ #category : #callbacks }
FFIExternalType >> callbackValueFor: anObject at: index [
"This is the value for a callback.
The callback parameters came from an external adress who can be treated as a ByteArray, so it
works the same as an FFIExternalArray (at least for now)"
^ self handle: anObject at: index
]
{ #category : #accessing }
FFIExternalType >> defaultReturnOnError [
"In case of a callback error, the image will try to show a debugger and that will most ot the
time crashes the VM (because it will break the process and will let a C function waiting and
and in incorrect state).
To prevent that, we use #on:fork:return: (look for senders) and, while forking the error to
allow user to debug his error, we also return a 'default' value, that may be also wrong."
^ self subclassResponsibility
]
{ #category : #'emitting code' }
FFIExternalType >> emitArgument: aBuilder context: aContext inCallout: aCallout [
self basicEmitArgument: aBuilder context: aContext inCallout: aCallout.
self needsArityPacking
ifTrue: [ self emitPointerArityRoll: aBuilder context: aContext ]
]
{ #category : #'stack parameter classification' }
FFIExternalType >> emitFlatStructureLayoutFieldInto: flatStructureLayout [
flatStructureLayout addField: self stackParameterClass size: self typeSize alignment: self typeAlignment
]
{ #category : #'emitting code' }
FFIExternalType >> emitPointerArityRoll: aBuilder context: aContext [
self loader
emitPointerArityPack: aBuilder
context: aContext
arity: self pointerArity
]
{ #category : #'emitting code' }
FFIExternalType >> emitReturn: aBuilder resultTempVar: resultVar context: aContext [
^ aBuilder returnTop
]
{ #category : #'emitting code' }
FFIExternalType >> emitReturn: aBuilder resultTempVar: resultVar context: aContext inCallout: aCallout [
^ self emitReturn: aBuilder resultTempVar: resultVar context: aContext
]
{ #category : #'emitting code' }
FFIExternalType >> emitReturnArgument: builder context: aContext [
"Some times functions need some post-process (for example, to unpack pointers).
This call MUST exit with result value in top of the stack (otherwise it will
interfere with emitReturn:resultTempVar:context:"
self loader
emitPointerArityUnpack: builder
type: self
context: aContext
]
{ #category : #accessing }
FFIExternalType >> externalType [
^ self class externalType
]
{ #category : #accessing }
FFIExternalType >> externalTypeAlignment [
^ self class externalTypeAlignment
]
{ #category : #accessing }
FFIExternalType >> externalTypeSize [
^ self class externalTypeSize
]
{ #category : #accessing }
FFIExternalType >> externalTypeWithArity [
^ self pointerArity > 0
ifTrue: [ self externalType asPointerType ]
ifFalse: [ self externalType ]
]
{ #category : #'accessing - array' }
FFIExternalType >> handle: aHandle at: index [
self isPointer ifTrue: [ ^ aHandle pointerAt: index ].
^ self basicHandle: aHandle at: index
]
{ #category : #'accessing - array' }
FFIExternalType >> handle: aHandle at: index put: value [
self isPointer ifTrue: [ ^ aHandle pointerAt: index put: value ].
^ self basicHandle: aHandle at: index put: value
]
{ #category : #initialization }
FFIExternalType >> initialize [
super initialize.
pointerArity := 0
]
{ #category : #testing }
FFIExternalType >> isExternalStructure [
^ false
]
{ #category : #testing }
FFIExternalType >> isExternalType [
^ true
]
{ #category : #testing }
FFIExternalType >> isFloatType [
^ false
]
{ #category : #testing }
FFIExternalType >> isPointer [
^ self pointerArity > 0
]
{ #category : #testing }
FFIExternalType >> isVoid [
^ false
]
{ #category : #accessing }
FFIExternalType >> loader [
^ loader
]
{ #category : #accessing }
FFIExternalType >> loader: aLoader [
loader := aLoader
]
{ #category : #testing }
FFIExternalType >> needsArityPacking [
"Regular types needs to be ''rolled'' if they are passed as pointers to its calling functions.
For example, executing consecutivelly this (simplified) two functions:
[[[
time := self ffiCall: #(time_t time(time_t* t) ). ""this will answer a long.""
self ffiCall: #(tm* localtime(time_t* time) ) ""this requires a pointer to time.""
]]]
This mechanism allows UnifiedFFI to perform the roll of this pointers for you (it performs
the equivallent of ==&time== in C).
For packing/unpacking logic, arity needs to be bigger than inherent type arity.
Means that if I have a type that is naturally a pointer (for example an ExternalAddress, who
is a 'void*'), it will have a natural arity of 1, then I pack if arity is bigger.
Other cases could need to be rolled when pointer arity is diffrent."
^ self pointerArity > self class naturalPointerArity
]
{ #category : #testing }
FFIExternalType >> needsArityUnpacking [
"Simple types do not need ''unpacking'' because they can not used as buffers (to receive values
from C functions).
For instance, in case you have functions with the form:
[[[
self ffiCall: #(void getPoint( double *x, double *y))
]]]
you cannot use instances of Float (since they are immutable in Pharo)... in that case you will
need to use an FFIExternalValueHolder."
^ false
]
{ #category : #'instance creation' }
FFIExternalType >> newBuffer [
"as its counterpart on class-side (newBuffer), this method get a memory segment that can be translated to C function as reference (to take later it's value).
For example:
ffi_getDimensions__width: width height: height
^ #( (void getDimensions( double *width, double *height) )
testMain
| widthBuffer heightBuffer width height |
widthBuffer := (FFIExternalType resolveType: #double) newBuffer.
heightBuffer := (FFIExternalType resolveType: #double) newBuffer.
ffi_getDimensions__width: widthBuffer height: heightBuffer.
width := widthBuffer doubleAt: 1.
height := heightBuffer doubleAt: 1.
Transcript crShow: width; tab; show: height.
==> 2.0 3.0
"
^ self class newBuffer
]
{ #category : #'emitting code' }
FFIExternalType >> offsetReadFieldAt: offsetVariableName [
^ self externalTypeWithArity offsetReadFieldAt: offsetVariableName
]
{ #category : #'emitting code' }
FFIExternalType >> offsetWriteFieldAt: offsetVariableName with: valueName [
^ self externalTypeWithArity
offsetWriteFieldAt: offsetVariableName
with: valueName
]
{ #category : #accessing }
FFIExternalType >> pointerAlignment [
^ self class pointerAlignment
]
{ #category : #accessing }
FFIExternalType >> pointerArity [
^ pointerArity
]
{ #category : #accessing }
FFIExternalType >> pointerArity: additionalArity [
pointerArity := pointerArity + additionalArity.
]
{ #category : #accessing }
FFIExternalType >> pointerSize [
^ self class pointerSize
]
{ #category : #printing }
FFIExternalType >> printOn: aStream [
super printOn: aStream.
pointerArity timesRepeat: [ aStream nextPut: $* ]
]
{ #category : #'emitting code' }
FFIExternalType >> readFieldAt: byteOffset [
^ self externalTypeWithArity readFieldAt: byteOffset
]
{ #category : #'stack parameter classification' }
FFIExternalType >> stackParameterClass [
^ self isPointer ifTrue: [ #integer ] ifFalse: [ self stackValueParameterClass ]
]
{ #category : #'stack parameter classification' }
FFIExternalType >> stackValueParameterClass [
self subclassResponsibility
]
{ #category : #accessing }
FFIExternalType >> typeAlignment [
"Answer a number of bytes, which receiver type takes in memory"
self pointerArity > 0 ifTrue: [ ^ self pointerAlignment ].
^ self externalTypeAlignment
]
{ #category : #accessing }
FFIExternalType >> typeSize [
"Answer a number of bytes, which receiver type takes in memory"
self pointerArity > 0 ifTrue: [ ^ self pointerSize ].
^ self externalTypeSize
]
{ #category : #testing }
FFIExternalType >> validateAfterParse: typeAndArityTuple [
"After parse an argument or return, some times I need to validate arity is correct.
This usually is ok, but since UFFI has types who do not have sense if they are not referenced as
pointer (check my overrides), I act as an ''after parse'' validation."
]
{ #category : #'emitting code' }
FFIExternalType >> writeFieldAt: byteOffset with: valueName [
^ self externalTypeWithArity
writeFieldAt: byteOffset
with: valueName
]