ffi.scm 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  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. procedure->external
  26. define-external-type
  27. define-foreign)
  28. (import (hoot cond-expand)
  29. (hoot inline-wasm)
  30. (hoot not)
  31. (hoot procedures)
  32. (hoot strings)
  33. (hoot gensym)
  34. (hoot errors)
  35. (hoot eq)
  36. (hoot syntax)
  37. (hoot pairs)
  38. (only (hoot lists) map)
  39. (hoot numbers)
  40. (hoot records)
  41. (hoot syntax-objects)
  42. (hoot write))
  43. (define (external? obj)
  44. (%inline-wasm
  45. '(func (param $obj (ref eq)) (result (ref eq))
  46. (ref.i31
  47. (if i32
  48. (ref.test $extern-ref (local.get $obj))
  49. (then (i32.const 17))
  50. (else (i32.const 1)))))
  51. obj))
  52. (define (external-null? extern)
  53. (check-type extern external? 'external-null?)
  54. (%inline-wasm
  55. '(func (param $extern (ref $extern-ref)) (result (ref eq))
  56. (if (ref eq)
  57. (ref.is_null
  58. (struct.get $extern-ref $val (local.get $extern)))
  59. (then (ref.i31 (i32.const 17)))
  60. (else (ref.i31 (i32.const 1)))))
  61. extern))
  62. (define (external-non-null? extern)
  63. (not (external-null? extern)))
  64. (define (procedure->external proc)
  65. (check-type proc procedure? 'procedure->external)
  66. (%inline-wasm
  67. '(func (param $f (ref $proc)) (result (ref eq))
  68. (struct.new $extern-ref
  69. (i32.const 0)
  70. (call $procedure->extern (local.get $f))))
  71. proc))
  72. ;; We are not using (hoot hashtables) here to avoid a dependency
  73. ;; cycle.
  74. (define (make-weak-map)
  75. (%inline-wasm
  76. '(func (result (ref eq))
  77. (struct.new $extern-ref
  78. (i32.const 0)
  79. (call $make-weak-map)))))
  80. (define (weak-map-ref weak-map key)
  81. (%inline-wasm
  82. '(func (param $weak-map (ref $extern-ref))
  83. (param $key (ref eq))
  84. (result (ref eq))
  85. (call $weak-map-get
  86. (ref.as_non_null
  87. (struct.get $extern-ref $val (local.get $weak-map)))
  88. (local.get $key)
  89. (ref.i31 (i32.const 1))))
  90. weak-map key))
  91. (define (weak-map-set! weak-map key value)
  92. (%inline-wasm
  93. '(func (param $weak-map (ref $extern-ref))
  94. (param $key (ref eq))
  95. (param $value (ref eq))
  96. (call $weak-map-set
  97. (ref.as_non_null
  98. (struct.get $extern-ref $val (local.get $weak-map)))
  99. (local.get $key)
  100. (local.get $value)))
  101. weak-map key value))
  102. ;; Analagous to Guile's define-wrapped-pointer-type.
  103. (define-syntax define-external-type
  104. (lambda (exp)
  105. (syntax-case exp ()
  106. ((_ name pred wrap unwrap print)
  107. (with-syntax ((%wrap (datum->syntax exp (gensym "wrap"))))
  108. #'(begin
  109. (define-record-type name
  110. #:printer print
  111. (%wrap extern)
  112. pred
  113. (extern unwrap))
  114. (define wrap
  115. (cond-expand
  116. (guile-vm %wrap)
  117. (hoot
  118. ;; Use a weak map so that if two externs are eq?
  119. ;; then their wrappers are also eq?
  120. (let ((table (make-weak-map)))
  121. (lambda (extern)
  122. (or (weak-map-ref table extern)
  123. (let ((wrapped (%wrap extern)))
  124. (weak-map-set! table extern wrapped)
  125. wrapped))))))))))
  126. ((_ name pred wrap unwrap)
  127. #'(define-external-type name pred wrap unwrap
  128. (lambda (obj port)
  129. (display "#<" port)
  130. (display 'name port)
  131. (display ">" port)))))))
  132. (define-syntax define-foreign
  133. (lambda (stx)
  134. (define (type-check exp proc-name)
  135. (define (check param predicate)
  136. #`(check-type #,param #,predicate '#,proc-name))
  137. (syntax-case exp (i32 i64 f32 f64 ref null eq string extern)
  138. ((x i32) (check #'x #'exact-integer?))
  139. ((x i64) (check #'x #'exact-integer?))
  140. ((x f32) (check #'x #'real?))
  141. ((x f64) (check #'x #'real?))
  142. ((x (ref eq)) #'#t)
  143. ((x (ref extern)) (check #'x #'external-non-null?))
  144. ((x (ref null extern)) (check #'x #'external?))
  145. ((x (ref string)) (check #'x #'string?))
  146. ((x type) (syntax-violation 'define-foreign "unsupported param type"
  147. stx #'type))))
  148. (define (import-result-types exp)
  149. (syntax-case exp (none)
  150. (none #'())
  151. (type #'((result type)))))
  152. (define (result-types exp)
  153. (syntax-case exp (none i32 i64 f32 f64 ref null string extern)
  154. (none #'())
  155. (i32 #'((result i64)))
  156. (i64 #'((result i64)))
  157. (f32 #'((result f64)))
  158. (f64 #'((result f64)))
  159. ((ref string) #'((result (ref eq))))
  160. ((ref null string) #'((result (ref eq))))
  161. ((ref extern) #'((result (ref eq))))
  162. ((ref null extern) #'((result (ref eq))))
  163. ((ref eq) #'((result (ref eq))))
  164. (type (syntax-violation 'define-foreign "unsupported result type"
  165. stx #'type))))
  166. (define (locals exp)
  167. (syntax-case exp (none i32 i64 f32 f64 ref null string extern)
  168. (none #'())
  169. (i32 #'())
  170. (i64 #'())
  171. (f32 #'())
  172. (f64 #'())
  173. ((ref string) #'())
  174. ((ref null string) #'((local $maybe-string (ref null string))))
  175. ((ref extern) #'())
  176. ((ref null extern) #'())
  177. ((ref eq) #'())
  178. (type (syntax-violation 'define-foreign "unsupported result type"
  179. stx #'type))))
  180. (define (lift-result exp)
  181. (syntax-case exp (none i32 i64 f32 f64 ref null string extern)
  182. ((x none) #'(x))
  183. ((x i32) #'((i64.extend_i32_s x)))
  184. ((x i64) #'(x))
  185. ((x f32) #'((f64.promote_f32 x)))
  186. ((x f64) #'(x))
  187. ((x (ref string)) #'((struct.new $string (i32.const 0) x)))
  188. ((x (ref null string))
  189. #'((local.set $maybe-string x)
  190. (if (ref eq)
  191. (ref.is_null (local.get $maybe-string))
  192. (then (ref.i31 (i32.const 1)))
  193. (else (struct.new $string (i32.const 0)
  194. (ref.as_non_null
  195. (local.get $maybe-string)))))))
  196. ((x (ref extern)) #'((struct.new $extern-ref (i32.const 0) x)))
  197. ((x (ref null extern)) #'((struct.new $extern-ref (i32.const 0) x)))
  198. ((x (ref eq)) #'((ref.cast $heap-object x)))
  199. (type (syntax-violation 'define-foreign "unsupported result type"
  200. stx #'type))))
  201. (define (fresh-wasm-id prefix)
  202. (datum->syntax stx (gensym prefix)))
  203. (define (fresh-wasm-ids prefix lst)
  204. (map (lambda (_) (fresh-wasm-id prefix)) lst))
  205. (syntax-case stx (->)
  206. ((_ proc-name mod name ptype ... -> rtype)
  207. (and (string? (syntax->datum #'mod)) (string? (syntax->datum #'name)))
  208. (with-syntax ((iname (fresh-wasm-id "$import-"))
  209. ((pname ...) (fresh-wasm-ids "$param-" #'(ptype ...))))
  210. #`(begin
  211. (cond-expand
  212. (guile-vm)
  213. (hoot
  214. (%wasm-import
  215. '(func iname (import mod name)
  216. (param ptype) ...
  217. #,@(import-result-types #'rtype)))))
  218. (define (proc-name pname ...)
  219. #,@(map (lambda (exp) (type-check exp #'proc-name))
  220. #'((pname ptype) ...))
  221. (%inline-wasm
  222. '(func (param pname ptype) ...
  223. #,@(result-types #'rtype)
  224. #,@(locals #'rtype)
  225. #,@(lift-result
  226. #'((call iname (local.get pname) ...) rtype)))
  227. pname ...)))))))))