ffi.scm 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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 hashtables)
  43. (hoot write))
  44. (define (external? obj)
  45. (%inline-wasm
  46. '(func (param $obj (ref eq)) (result (ref eq))
  47. (ref.i31
  48. (if i32
  49. (ref.test $extern-ref (local.get $obj))
  50. (then (i32.const 17))
  51. (else (i32.const 1)))))
  52. obj))
  53. (define (external-null? extern)
  54. (check-type extern external? 'external-null?)
  55. (%inline-wasm
  56. '(func (param $extern (ref $extern-ref)) (result (ref eq))
  57. (if (ref eq)
  58. (ref.is_null
  59. (struct.get $extern-ref $val (local.get $extern)))
  60. (then (ref.i31 (i32.const 17)))
  61. (else (ref.i31 (i32.const 1)))))
  62. extern))
  63. (define (external-non-null? extern)
  64. (not (external-null? extern)))
  65. (define (procedure->external proc)
  66. (check-type proc procedure? 'procedure->external)
  67. (%inline-wasm
  68. '(func (param $f (ref $proc)) (result (ref eq))
  69. (struct.new $extern-ref
  70. (i32.const 0)
  71. (call $procedure->extern (local.get $f))))
  72. proc))
  73. ;; Analagous to Guile's define-wrapped-pointer-type.
  74. (define-syntax define-external-type
  75. (lambda (exp)
  76. (syntax-case exp ()
  77. ((_ name pred wrap unwrap print)
  78. (with-syntax ((%wrap (datum->syntax exp (gensym "wrap"))))
  79. #'(begin
  80. (define-record-type name
  81. #:printer print
  82. (%wrap extern)
  83. pred
  84. (extern unwrap))
  85. (define wrap
  86. ;; FIXME: Can't use (hoot hashtables) at compile
  87. ;; time.
  88. (cond-expand
  89. (guile-vm %wrap)
  90. (hoot
  91. ;; Use a weak table so that if two externs are eq?
  92. ;; then their wrappers are also eq?
  93. (let ((table (make-weak-key-hashtable)))
  94. (lambda (extern)
  95. (or (weak-key-hashtable-ref table extern)
  96. (let ((wrapped (%wrap extern)))
  97. (weak-key-hashtable-set! table extern wrapped)
  98. wrapped))))))))))
  99. ((_ name pred wrap unwrap)
  100. #'(define-external-type name pred wrap unwrap
  101. (lambda (obj port)
  102. (display "#<" port)
  103. (display 'name port)
  104. (display ">" port)))))))
  105. (define-syntax define-foreign
  106. (lambda (x)
  107. (define (type-check exp proc-name)
  108. (define (check param predicate)
  109. #`(check-type #,param #,predicate '#,proc-name))
  110. (syntax-case exp (i32 i64 f32 f64 ref null eq string extern)
  111. ((x i32) (check #'x #'exact-integer?))
  112. ((x i64) (check #'x #'exact-integer?))
  113. ((x f32) (check #'x #'real?))
  114. ((x f64) (check #'x #'real?))
  115. ((x (ref eq)) #'#t)
  116. ((x (ref extern)) (check #'x #'external-non-null?))
  117. ((x (ref null extern)) (check #'x #'external?))
  118. ((x (ref string)) (check #'x #'string?))
  119. ((x type) (%error "unsupported param type" #'type))))
  120. (define (import-result-types exp)
  121. (syntax-case exp (none)
  122. (none #'())
  123. (type #'((result type)))))
  124. (define (result-types exp)
  125. (syntax-case exp (none i32 i64 f32 f64 ref null string extern)
  126. (none #'())
  127. (i32 #'((result i64)))
  128. (i64 #'((result i64)))
  129. (f32 #'((result f64)))
  130. (f64 #'((result f64)))
  131. ((ref string) #'((result (ref eq))))
  132. ((ref null string) #'((result (ref eq))))
  133. ((ref extern) #'((result (ref eq))))
  134. ((ref null extern) #'((result (ref eq))))
  135. ((ref eq) #'((result (ref eq))))
  136. (type (%error "unsupported result type" #'type))))
  137. (define (locals exp)
  138. (syntax-case exp (none i32 i64 f32 f64 ref null string extern)
  139. (none #'())
  140. (i32 #'())
  141. (i64 #'())
  142. (f32 #'())
  143. (f64 #'())
  144. ((ref string) #'())
  145. ((ref null string) #'((local $maybe-string (ref null string))))
  146. ((ref extern) #'())
  147. ((ref null extern) #'())
  148. ((ref eq) #'())
  149. (type (%error "unsupported result type" #'type))))
  150. (define (lift-result exp)
  151. (syntax-case exp (none i32 i64 f32 f64 ref null string extern)
  152. ((x none) #'(x))
  153. ((x i32) #'((i64.extend_i32_s x)))
  154. ((x i64) #'(x))
  155. ((x f32) #'((f64.promote_f32 x)))
  156. ((x f64) #'(x))
  157. ((x (ref string)) #'((struct.new $string (i32.const 0) x)))
  158. ((x (ref null string))
  159. #'((local.set $maybe-string x)
  160. (if (ref eq)
  161. (ref.is_null (local.get $maybe-string))
  162. (then (ref.i31 (i32.const 1)))
  163. (else (struct.new $string (i32.const 0)
  164. (ref.as_non_null
  165. (local.get $maybe-string)))))))
  166. ((x (ref extern)) #'((struct.new $extern-ref (i32.const 0) x)))
  167. ((x (ref null extern)) #'((struct.new $extern-ref (i32.const 0) x)))
  168. ((x (ref eq)) #'((ref.cast $heap-object x)))
  169. (type (%error "unsupported result type" #'type))))
  170. (define (fresh-wasm-id prefix)
  171. (datum->syntax x (gensym prefix)))
  172. (define (fresh-wasm-ids prefix lst)
  173. (map (lambda (_) (fresh-wasm-id prefix)) lst))
  174. (syntax-case x (->)
  175. ((_ proc-name mod name ptype ... -> rtype)
  176. (and (string? (syntax->datum #'mod)) (string? (syntax->datum #'name)))
  177. (with-syntax ((iname (fresh-wasm-id "$import-"))
  178. ((pname ...) (fresh-wasm-ids "$param-" #'(ptype ...))))
  179. #`(begin
  180. (cond-expand
  181. (guile-vm)
  182. (hoot
  183. (%wasm-import
  184. '(func iname (import mod name)
  185. (param ptype) ...
  186. #,@(import-result-types #'rtype)))))
  187. (define (proc-name pname ...)
  188. #,@(map (lambda (exp) (type-check exp #'proc-name))
  189. #'((pname ptype) ...))
  190. (%inline-wasm
  191. '(func (param pname ptype) ...
  192. #,@(result-types #'rtype)
  193. #,@(locals #'rtype)
  194. #,@(lift-result
  195. #'((call iname (local.get pname) ...) rtype)))
  196. pname ...)))))))))