ffi.scm 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. ;;; Hoot foreign function interface
  2. ;;; Copyright (C) 2023, 2024 David Thompson <dave@spritely.institute>
  3. ;;;
  4. ;;; Licensed under the Apache License, Version 2.0 (the "License");
  5. ;;; you may not use this file except in compliance with the License.
  6. ;;; You may obtain a copy of the License at
  7. ;;;
  8. ;;; http://www.apache.org/licenses/LICENSE-2.0
  9. ;;;
  10. ;;; Unless required by applicable law or agreed to in writing, software
  11. ;;; distributed under the License is distributed on an "AS IS" BASIS,
  12. ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. ;;; See the License for the specific language governing permissions and
  14. ;;; limitations under the License.
  15. ;;; Commentary:
  16. ;;;
  17. ;;; Foreign function interface for declaring Wasm host imports and
  18. ;;; handling (ref extern) values.
  19. ;;;
  20. ;;; Code:
  21. (library (hoot ffi)
  22. (export external?
  23. external-null?
  24. external-non-null?
  25. external-function?
  26. call-external
  27. procedure->external
  28. define-external-type
  29. define-foreign)
  30. (import (hoot cond-expand)
  31. (hoot inline-wasm)
  32. (hoot not)
  33. (hoot procedures)
  34. (hoot strings)
  35. (hoot gensym)
  36. (hoot errors)
  37. (hoot eq)
  38. (hoot syntax)
  39. (hoot pairs)
  40. (only (hoot lists) map)
  41. (hoot numbers)
  42. (hoot records)
  43. (hoot syntax-objects)
  44. (hoot write))
  45. (define (external? obj)
  46. (%inline-wasm
  47. '(func (param $obj (ref eq)) (result (ref eq))
  48. (ref.i31
  49. (if i32
  50. (ref.test $extern-ref (local.get $obj))
  51. (then (i32.const 17))
  52. (else (i32.const 1)))))
  53. obj))
  54. (define (external-null? extern)
  55. (check-type extern external? 'external-null?)
  56. (%inline-wasm
  57. '(func (param $extern (ref $extern-ref)) (result (ref eq))
  58. (if (ref eq)
  59. (ref.is_null
  60. (struct.get $extern-ref $val (local.get $extern)))
  61. (then (ref.i31 (i32.const 17)))
  62. (else (ref.i31 (i32.const 1)))))
  63. extern))
  64. (define (external-non-null? extern)
  65. (not (external-null? extern)))
  66. (define (procedure->external proc)
  67. (check-type proc procedure? 'procedure->external)
  68. (%inline-wasm
  69. '(func (param $f (ref $proc)) (result (ref eq))
  70. (struct.new $extern-ref
  71. (i32.const 0)
  72. (call $procedure->extern (local.get $f))))
  73. proc))
  74. (define (external-function? obj)
  75. (and (external? obj)
  76. (external-non-null? obj)
  77. (%inline-wasm
  78. '(func (param $obj (ref extern)) (result (ref eq))
  79. (if (ref eq)
  80. (i32.eqz (call $extern-func? (local.get $obj)))
  81. (then (ref.i31 (i32.const 1)))
  82. (else (ref.i31 (i32.const 17)))))
  83. obj)))
  84. (define (call-external func . args)
  85. (check-type func external-function? 'call-external)
  86. (%inline-wasm
  87. '(func (param $func (ref extern)) (param $args (ref eq)) (result (ref eq))
  88. (call $call-extern (local.get $func) (local.get $args)))
  89. func args))
  90. ;; We are not using (hoot hashtables) here to avoid a dependency
  91. ;; cycle.
  92. (define (make-weak-map)
  93. (%inline-wasm
  94. '(func (result (ref eq))
  95. (struct.new $extern-ref
  96. (i32.const 0)
  97. (call $make-weak-map)))))
  98. (define (weak-map-ref weak-map key)
  99. (%inline-wasm
  100. '(func (param $weak-map (ref $extern-ref))
  101. (param $key (ref eq))
  102. (result (ref eq))
  103. (call $weak-map-get
  104. (ref.as_non_null
  105. (struct.get $extern-ref $val (local.get $weak-map)))
  106. (local.get $key)
  107. (ref.i31 (i32.const 1))))
  108. weak-map key))
  109. (define (weak-map-set! weak-map key value)
  110. (%inline-wasm
  111. '(func (param $weak-map (ref $extern-ref))
  112. (param $key (ref eq))
  113. (param $value (ref eq))
  114. (call $weak-map-set
  115. (ref.as_non_null
  116. (struct.get $extern-ref $val (local.get $weak-map)))
  117. (local.get $key)
  118. (local.get $value)))
  119. weak-map key value))
  120. ;; Analagous to Guile's define-wrapped-pointer-type.
  121. (define-syntax define-external-type
  122. (lambda (exp)
  123. (syntax-case exp ()
  124. ((_ name pred wrap unwrap print)
  125. (with-syntax ((%wrap (datum->syntax exp (gensym "wrap"))))
  126. #'(begin
  127. (define-record-type name
  128. #:printer print
  129. (%wrap extern)
  130. pred
  131. (extern unwrap))
  132. (define wrap
  133. (cond-expand
  134. (guile-vm %wrap)
  135. (hoot
  136. ;; Use a weak map so that if two externs are eq?
  137. ;; then their wrappers are also eq?
  138. (let ((table (make-weak-map)))
  139. (lambda (extern)
  140. (or (weak-map-ref table extern)
  141. (let ((wrapped (%wrap extern)))
  142. (weak-map-set! table extern wrapped)
  143. wrapped))))))))))
  144. ((_ name pred wrap unwrap)
  145. #'(define-external-type name pred wrap unwrap
  146. (lambda (obj port)
  147. (display "#<" port)
  148. (display 'name port)
  149. (display ">" port)))))))
  150. (define-syntax define-foreign
  151. (lambda (stx)
  152. (define (type-check exp proc-name)
  153. (define (check param predicate)
  154. #`(check-type #,param #,predicate '#,proc-name))
  155. (syntax-case exp (i32 i64 f32 f64 ref null eq string extern)
  156. ((x i32) (check #'x #'exact-integer?))
  157. ((x i64) (check #'x #'exact-integer?))
  158. ((x f32) (check #'x #'real?))
  159. ((x f64) (check #'x #'real?))
  160. ((x (ref eq)) #'#t)
  161. ((x (ref extern)) (check #'x #'external-non-null?))
  162. ((x (ref null extern)) (check #'x #'external?))
  163. ((x (ref string)) (check #'x #'string?))
  164. ((x type) (syntax-violation 'define-foreign "unsupported param type"
  165. stx #'type))))
  166. (define (import-result-types exp)
  167. (syntax-case exp (none)
  168. (none #'())
  169. (type #'((result type)))))
  170. (define (result-types exp)
  171. (syntax-case exp (none i32 i64 f32 f64 ref null string extern)
  172. (none #'())
  173. (i32 #'((result i64)))
  174. (i64 #'((result i64)))
  175. (f32 #'((result f64)))
  176. (f64 #'((result f64)))
  177. ((ref string) #'((result (ref eq))))
  178. ((ref null string) #'((result (ref eq))))
  179. ((ref extern) #'((result (ref eq))))
  180. ((ref null extern) #'((result (ref eq))))
  181. ((ref eq) #'((result (ref eq))))
  182. (type (syntax-violation 'define-foreign "unsupported result type"
  183. stx #'type))))
  184. (define (locals exp)
  185. (syntax-case exp (none i32 i64 f32 f64 ref null string extern)
  186. (none #'())
  187. (i32 #'())
  188. (i64 #'())
  189. (f32 #'())
  190. (f64 #'())
  191. ((ref string) #'())
  192. ((ref null string) #'((local $maybe-string (ref null string))))
  193. ((ref extern) #'())
  194. ((ref null extern) #'())
  195. ((ref eq) #'())
  196. (type (syntax-violation 'define-foreign "unsupported result type"
  197. stx #'type))))
  198. (define (lift-result exp)
  199. (syntax-case exp (none i32 i64 f32 f64 ref null string extern)
  200. ((x none) #'(x))
  201. ((x i32) #'((i64.extend_i32_s x)))
  202. ((x i64) #'(x))
  203. ((x f32) #'((f64.promote_f32 x)))
  204. ((x f64) #'(x))
  205. ((x (ref string)) #'((struct.new $string (i32.const 0) x)))
  206. ((x (ref null string))
  207. #'((local.set $maybe-string x)
  208. (if (ref eq)
  209. (ref.is_null (local.get $maybe-string))
  210. (then (ref.i31 (i32.const 1)))
  211. (else (struct.new $string (i32.const 0)
  212. (ref.as_non_null
  213. (local.get $maybe-string)))))))
  214. ((x (ref extern)) #'((struct.new $extern-ref (i32.const 0) x)))
  215. ((x (ref null extern)) #'((struct.new $extern-ref (i32.const 0) x)))
  216. ((x (ref eq)) #'((ref.cast $heap-object x)))
  217. (type (syntax-violation 'define-foreign "unsupported result type"
  218. stx #'type))))
  219. (define (fresh-wasm-id prefix)
  220. (datum->syntax stx (gensym prefix)))
  221. (define (fresh-wasm-ids prefix lst)
  222. (map (lambda (_) (fresh-wasm-id prefix)) lst))
  223. (syntax-case stx (->)
  224. ((_ proc-name mod name ptype ... -> rtype)
  225. (and (string? (syntax->datum #'mod)) (string? (syntax->datum #'name)))
  226. (with-syntax ((iname (fresh-wasm-id "$import-"))
  227. ((pname ...) (fresh-wasm-ids "$param-" #'(ptype ...))))
  228. #`(begin
  229. (cond-expand
  230. (guile-vm)
  231. (hoot
  232. (%wasm-import
  233. '(func iname (import mod name)
  234. (param ptype) ...
  235. #,@(import-result-types #'rtype)))))
  236. (define (proc-name pname ...)
  237. #,@(map (lambda (exp) (type-check exp #'proc-name))
  238. #'((pname ptype) ...))
  239. (%inline-wasm
  240. '(func (param pname ptype) ...
  241. #,@(result-types #'rtype)
  242. #,@(locals #'rtype)
  243. #,@(lift-result
  244. #'((call iname (local.get pname) ...) rtype)))
  245. pname ...)))))))))