luv

Workshop wiki

runtime.lisp

objective-c/runtime.lisp

system luv/objective-c · 47 definitions · on GitHub

A small declared Objective-C foreign object system.

Classes and objects preserve native identity at the Lisp boundary. A message declaration is a class of invocations whose metaclass owns the selector, exact ABI, and ownership convention. INVOKE dispatches on both the runtime and that message class; tracing composes as an invoker mixin.

in-package#:luv.objective-c
eval-when
:compile-toplevel:load-toplevel:execute
cffi:define-foreign-libraryobjective-c-runtime-library
:darwin
:or"libobjc.A.dylib""libobjc.dylib"
cffi:define-foreign-libraryfoundation-framework
:darwin
:framework"Foundation"
cffi:use-foreign-libraryobjective-c-runtime-library
cffi:use-foreign-libraryfoundation-framework
defmacrowith-objective-c-native-environment
&bodybody

Run body with the floating-point environment expected by Apple frameworks.

#+sbcl`
sb-int:with-float-traps-masked
:invalid:divide-by-zero:overflow:underflow:inexact
,@body
#-sbcl `(progn ,@body)
cffi:defcfun
"objc_getClass"%objc-get-class:libraryobjective-c-runtime-library
:pointer
name:string
cffi:defcfun
"sel_registerName"%sel-register-name:libraryobjective-c-runtime-library
:pointer
name:string
cffi:defcfun
"object_getClassName"%object-get-class-name:libraryobjective-c-runtime-library
:pointer
object:pointer
define-conditionobjective-c-error
error
define-conditionobjective-c-message-error
message:initarg:message:readerobjective-c-exception-message
receiver:initarg:receiver:readerobjective-c-exception-receiver
selector:initarg:selector:readerobjective-c-exception-selector
name:initarg:name:readerobjective-c-exception-name
reason:initarg:reason:readerobjective-c-exception-reason
call-stack:initarg:call-stack:readerobjective-c-exception-call-stack
define-conditionobjective-c-exception
:report
lambda
conditionstream
formatstream"Objective-C exception ~A while sending ~A: ~A"
or
objective-c-exception-namecondition
"<unnamed>"
objective-c-exception-selectorcondition
or
objective-c-exception-reasoncondition
"<no reason>"
define-conditionobjective-c-bridge-error
:report
lambda
conditionstream
formatstream"Objective-C bridge rejected ~A: ~A"
objective-c-exception-selectorcondition
or
objective-c-exception-reasoncondition
"<no reason>"
:documentation

The native boundary could not perform a declared Objective-C message.

define-conditionunknown-objective-c-class
name:initarg:name:readerunknown-objective-c-class-name
:report
lambda
conditionstream
formatstream"Objective-C class ~S is not registered."
unknown-objective-c-class-namecondition
define-conditionreleased-objective-c-object
object:initarg:object:readerreleased-objective-c-object-object
:report
lambda
conditionstream
formatstream"Objective-C object ~S has already been released."
released-objective-c-object-objectcondition
define-conditionobjective-c-ownership-error
object:initarg:object:readerobjective-c-ownership-error-object
:report
lambda
conditionstream
formatstream"Objective-C object ~S does not own a retain to consume."
objective-c-ownership-error-objectcondition
defclassobjective-c-receiver
:documentation

Something that can receive an Objective-C message.

defclassobjective-c-class
name:initarg:name:readerobjective-c-class-name
pointer:initarg:pointer:reader%objective-c-class-pointer
:documentation

A borrowed Objective-C runtime Class identity.

defclassobjective-c-object
pointer:initarg:pointer:reader%objective-c-object-pointer
class-name:initarg:class-name:readerobjective-c-object-class-name
protocol-name:initarg:protocol-name:initformnil:readerobjective-c-object-protocol-name
ownership:initarg:ownership:readerobjective-c-object-ownership
released-p:initformnil:accessorobjective-c-object-released-p
:documentation

One native Objective-C pointer and exactly one owned or borrowed claim.

defmethodprint-object
print-unreadable-object
classstream:typet:identitynil
formatstream"~A 0x~X"
objective-c-class-nameclass
cffi:pointer-address
%objective-c-class-pointerclass
defmethodprint-object
stream
print-unreadable-object
objectstream:typet:identitynil
formatstream"~A~@[ as ~A~] ~A~:[~; released~] 0x~X"
objective-c-object-class-nameobject
objective-c-object-protocol-nameobject
objective-c-object-ownershipobject
objective-c-object-released-pobject
cffi:pointer-address
%objective-c-object-pointerobject
defgenericobjective-c-pointer
receiver
:documentation

Return receiver's live native pointer for one ABI crossing.

defmethodobjective-c-pointer
%objective-c-class-pointerreceiver
defmethodobjective-c-pointer
when
objective-c-object-released-pobject
error'released-objective-c-object:objectobject
%objective-c-object-pointerobject
defmethodobjective-c-pointer
nothingnull
cffi:null-pointer
defunfind-objective-c-class
name

Return the registered Objective-C class name as a borrowed receiver.

let
pointer
%objc-get-classname
when
cffi:null-pointer-ppointer
make-instance'objective-c-class:namename:pointerpointer
defunobjective-c-runtime-class-name
pointer
let
name
%object-get-class-namepointer
if
cffi:null-pointer-pname
"<unknown>"
cffi:foreign-string-to-lispname
defunwrap-objective-c-object
pointer&key
ownership:borrowed
protocol-name

Wrap pointer with one explicit :OWNED or :BORROWED claim; NIL represents nil.

when
cffi:null-pointer-ppointer
unless
memberownership'
:owned:borrowed
error"Unknown Objective-C ownership ~S."ownership
make-instance'objective-c-object:pointerpointer:class-name:protocol-nameprotocol-name:ownershipownership
defunobjective-c-object=
leftright

Whether left and right wrap the same native Objective-C identity.

and
=
cffi:pointer-address
cffi:pointer-address
defstructobjective-c-message-definitionnameselectorselector-pointerresult-typeresult-ownershipresult-class-nameconsumes-receiver-parguments
defvar*objective-c-message-definitions*
make-hash-table:test#'eq
defstructobjective-c-message-eventsequencetimestampdurationthreadnameargumentsvaluesstatuscondition
defstruct
objective-c-trace
:constructor%make-objective-c-trace
:conc-name%objective-c-trace-
started-at0.0d0
next-sequence0
events
make-array0:adjustablet:fill-pointer0
#+sb-thread
lock
sb-thread:make-mutex:name"luv Objective-C trace"
defvar*objective-c-trace*nil"The current opt-in Objective-C trace, or NIL on the ordinary direct path."
defunobjective-c-trace-now
/
get-internal-real-time
coerceinternal-time-units-per-second'double-float
defmacrowith-objective-c-trace-lock
trace
&bodybody
#+sb-thread`
sb-thread:with-mutex
%objective-c-trace-lock,trace
,@body
#-sb-thread `(progn ,@body)
defunmake-objective-c-trace
%make-objective-c-trace:started-at
defunobjective-c-trace-events
trace
with-objective-c-trace-lock
trace
sort
coerce
copy-seq
%objective-c-trace-eventstrace
'list
#'<:key#'objective-c-message-event-sequence
defunobjective-c-trace-thread-name
#+sb-thread
or
sb-thread:thread-namesb-thread:*current-thread*
"unnamed thread"
#-sb-thread "single thread"
defunsnapshot-objective-c-value
value&optional
depth0
cond
list:objective-c-class:name
objective-c-class-namevalue
:pointer
cffi:pointer-address
%objective-c-class-pointervalue
list:objective-c-object:class
objective-c-object-class-namevalue
:protocol
objective-c-object-protocol-namevalue
:ownership
objective-c-object-ownershipvalue
:released
objective-c-object-released-pvalue
:pointer
cffi:pointer-address
%objective-c-object-pointervalue
cffi:pointerpvalue
list:pointer
cffi:pointer-addressvalue
or
nullvalue
symbolpvalue
numberpvalue
characterpvalue
value
stringpvalue
copy-seqvalue
>=depth6
list:object
type-ofvalue
conspvalue
vectorpvalue
map'vector
lambda
item
value
t
list:object
type-ofvalue
handler-case
princ-to-stringvalue
error
"<unprintable>"
eval-when
:compile-toplevel:load-toplevel:execute
defunobjective-c-foreign-type
casetype
:object:class:selector
:pointer
otherwisetype
defunvalidate-objective-c-message-declaration
nameresult-typeownershipconsumes-receiver-parguments
when
and
eqresult-type:object
nullownership
error"Objective-C object result ~S must declare :OWNERSHIP."name
when
and
not
eqresult-type:object
ownership
error"Non-object Objective-C result ~S cannot declare :OWNERSHIP."name
unless
memberownership'
nil:owned:borrowed
error"Unknown Objective-C result ownership ~S in ~S."ownershipname
dolist
argumentarguments
unless
and
listpargument
=
lengthargument
2
symbolp
firstargument
error"Malformed Objective-C argument ~S in ~S."argumentname
when
andconsumes-receiver-p
eqresult-type:object
error"Consuming Objective-C message ~S cannot also return an object."name
defunregister-objective-c-message-definition
nameselectorresult-typeownershipresult-class-nameconsumes-receiver-parguments
let
definition
make-objective-c-message-definition:namename:selectorselector:selector-pointer
%sel-register-nameselector
:result-typeresult-type:result-ownershipownership:result-class-nameresult-class-name:consumes-receiver-pconsumes-receiver-p:arguments
cons'
receiver:object
arguments
definition
defunobjective-c-argument-form
nametype
casetype
:object:class
`
otherwisename
defunobjective-c-message-description
name

Return the selector, ABI, and ownership metadata for one declared message.

let
definition
or
error"No Objective-C message named ~S."name
list:selector
objective-c-message-definition-selectordefinition
:result-type
objective-c-message-definition-result-typedefinition
:result-ownership
objective-c-message-definition-result-ownershipdefinition
:result-class
objective-c-message-definition-result-class-namedefinition
:consumes-receiver
objective-c-message-definition-consumes-receiver-pdefinition
:argument-types
objective-c-message-definition-argumentsdefinition
defunobjective-c-message-event-description
event

Return one opt-in trace event with its declared ABI metadata.

append
objective-c-message-description
objective-c-message-event-nameevent
list:status
objective-c-message-event-statusevent
:arguments
objective-c-message-event-argumentsevent
:values
objective-c-message-event-valuesevent
:condition
objective-c-message-event-conditionevent
defvar*objective-c-exception-policy*:unchecked

How declared messages cross the native boundary: :CATCH or :UNCHECKED.

:UNCHECKED calls objc_msgSend directly and is the ordinary path. :CATCH uses the native NSInvocation exception bridge for an explicit diagnostic scope.

defmacrowith-unchecked-objective-c-messages
&bodybody

Send declared messages in body directly, without catching NSException.

`
defmacrowith-objective-c-exception-handling
&bodybody

Catch NSException in body, even inside an unchecked dynamic context.

`
defmacrowith-objective-c-trace
trace
&bodybody

Run body with declared sends recorded as backend-local trace events.

`
defvar*objective-c-message-send-pointer*
cffi:foreign-symbol-pointer"objc_msgSend":library'objective-c-runtime-library

The stable libobjc dispatch entry, resolved once when this runtime loads.

Resolving this symbol for every declared message used to put dlsym on the per-frame path hundreds of times. #WEE1DX

defgenericcheck-consumable-objective-c-receiver
receiver
:documentation

Validate that receiver owns the retain a message consumes.

defmethodcheck-consumable-objective-c-receiver
unless
eq
objective-c-object-ownershipreceiver
:owned
error'objective-c-ownership-error:objectreceiver
defuntranslate-objective-c-result
if
eq
objective-c-message-definition-result-typedefinition
:object
wrap-objective-c-objectvalue:ownership
objective-c-message-definition-result-ownershipdefinition
:protocol-name
objective-c-message-definition-result-class-namedefinition
value
defuncall-with-objective-c-message-trace
tracedefinitionargumentsfunction
let*
event
make-objective-c-message-event:sequence
with-objective-c-trace-lock
trace
prog1
%objective-c-trace-next-sequencetrace
incf
%objective-c-trace-next-sequencetrace
:timestamp
-started-at
%objective-c-trace-started-attrace
:thread:name
objective-c-message-definition-namedefinition
:arguments
mapcar
lambda
argument
list
firstargument
arguments
:status:signaled
handler-bind
error
lambda
condition
setf
objective-c-message-event-conditionevent
list:type
type-ofcondition
:message
handler-case
princ-to-stringcondition
error
"<unprintable condition>"
unwind-protect
let
results
multiple-value-list
funcallfunction
setf
objective-c-message-event-statusevent
:returned
objective-c-message-event-valuesevent
values-listresults
setf
objective-c-message-event-durationevent
with-objective-c-trace-lock
trace
vector-push-extendevent
%objective-c-trace-eventstrace
eval-when
:compile-toplevel:load-toplevel:execute
defuncaught-objective-c-message-send-form
definitionreceiverresult-typearguments
let
result-storage
gensym"RESULT-STORAGE"
argument-array
gensym"ARGUMENT-ARRAY"
argument-size-array
gensym"ARGUMENT-SIZE-ARRAY"
argument-storages
looprepeat
lengtharguments
collect
gensym"ARGUMENT"
labels
call-form
resultresult-size
ifarguments`
cffi:with-foreign-objects
,argument-array:pointer,
lengtharguments
,argument-size-array:size,
lengtharguments
,@
loopforstorageinargument-storagesforindexfrom0for
niltype
inargumentscollect`
setf
cffi:mem-aref,argument-array:pointer,index
,storage
cffi:mem-aref,argument-size-array:size,index
cffi:foreign-type-size',
objective-c-foreign-typetype
call-with-objective-c-exception-boundary,definition,receiver,result,result-size,argument-array,argument-size-array,
lengtharguments
`
call-with-objective-c-exception-boundary,definition,receiver,result,result-size
cffi:null-pointer
cffi:null-pointer
0
result-form
if
eqresult-type:void
`
progn,
call-form'
cffi:null-pointer
0
nil
let
foreign-result-type
objective-c-foreign-typeresult-type
`
cffi:with-foreign-object
,result-storage',foreign-result-type
,
call-formresult-storage`
cffi:foreign-type-size',foreign-result-type
cffi:mem-ref,result-storage',foreign-result-type
argument-forms
remaining-argumentsremaining-storages
ifremaining-arguments
destructuring-bind
nametype
.tail
remaining-arguments
`
cffi:with-foreign-object
,
firstremaining-storages
',
objective-c-foreign-typetype
setf
cffi:mem-ref,
firstremaining-storages
',
objective-c-foreign-typetype
,
objective-c-argument-formnametype
,
argument-formstail
restremaining-storages
result-form
argument-formsargumentsargument-storages
defununchecked-objective-c-message-send-form
definitionreceiverresult-typearguments
`
cffi:foreign-funcall-pointer*objective-c-message-send-pointer*
:pointer:pointer
objective-c-message-definition-selector-pointer,definition
,@
loopfor
argument-nameargument-type
inargumentsappend
list
objective-c-foreign-typeargument-type
objective-c-argument-formargument-nameargument-type
,
objective-c-foreign-typeresult-type
defmacrodefine-objective-c-message
name
selectorresult-type&keyownershipclassconsumes-receiver
&bodyarguments

Define name as an inspectable, ABI-typed class and message-sending function.

validate-objective-c-message-declarationnameresult-typeownershipconsumes-receiverarguments
let
argument-names
mapcar#'firstarguments
definition-name
intern
formatnil"*~A-DEFINITION*"name
symbol-packagename
`
progn
defparameter,definition-name
register-objective-c-message-definition',name,selector',result-type',ownership,class,consumes-receiver',arguments
defun,name
receiver,@argument-names
,
whenconsumes-receiver'
flet
send
let
result
with-objective-c-native-environment
ecase*objective-c-exception-policy*
:catch,
caught-objective-c-message-send-formdefinition-name'receiverresult-typearguments
:unchecked,
unchecked-objective-c-message-send-formdefinition-name'receiverresult-typearguments
,
whenconsumes-receiver'
setf
objective-c-object-released-preceiver
t
translate-objective-c-resultresult,definition-name
if*objective-c-trace*
call-with-objective-c-message-trace*objective-c-trace*,definition-name
list
list'receiverreceiver
,@
loopforargument-nameinargument-namescollect`
list',argument-name,argument-name
#'send
send
',name