ffi.scm 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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-foreign)
  27. (import (hoot primitives)
  28. (hoot not)
  29. (hoot procedures)
  30. (hoot strings)
  31. (hoot errors)
  32. (hoot pairs)
  33. (only (hoot lists) map)
  34. (hoot numbers))
  35. (define (external? obj)
  36. (%inline-wasm
  37. '(func (param $obj (ref eq)) (result (ref eq))
  38. (ref.i31
  39. (if i32
  40. (ref.test $extern-ref (local.get $obj))
  41. (then (i32.const 17))
  42. (else (i32.const 1)))))
  43. obj))
  44. (define (external-null? extern)
  45. (check-type extern external? 'external-null?)
  46. (%inline-wasm
  47. '(func (param $extern (ref $extern-ref)) (result (ref eq))
  48. (if (ref eq)
  49. (ref.is_null
  50. (struct.get $extern-ref $val (local.get $extern)))
  51. (then (ref.i31 (i32.const 17)))
  52. (else (ref.i31 (i32.const 1)))))
  53. extern))
  54. (define (external-non-null? extern)
  55. (not (external-null? extern)))
  56. (define (procedure->external proc)
  57. (check-type proc procedure? 'procedure->external)
  58. (%inline-wasm
  59. '(func (param $f (ref $proc)) (result (ref eq))
  60. (struct.new $extern-ref
  61. (i32.const 0)
  62. (call $procedure->extern (local.get $f))))
  63. proc))
  64. (define-syntax define-foreign
  65. (lambda (x)
  66. (define (type-check exp proc-name)
  67. (define (check param predicate)
  68. #`(check-type #,param #,predicate '#,proc-name))
  69. (syntax-case exp (i32 i64 f32 f64 ref null eq string extern)
  70. ((x i32) (check #'x #'exact-integer?))
  71. ((x i64) (check #'x #'exact-integer?))
  72. ((x f32) (check #'x #'real?))
  73. ((x f64) (check #'x #'real?))
  74. ((x (ref eq)) #'#t)
  75. ((x (ref extern)) (check #'x #'external-non-null?))
  76. ((x (ref null extern)) (check #'x #'external?))
  77. ((x (ref string)) (check #'x #'string?))
  78. ((x type) (%error "unsupported param type" #'type))))
  79. (define (import-result-types exp)
  80. (syntax-case exp (none)
  81. (none #'())
  82. (type #'((result type)))))
  83. (define (result-types exp)
  84. (syntax-case exp (none i32 i64 f32 f64 ref null string extern)
  85. (none #'())
  86. (i32 #'((result i64)))
  87. (i64 #'((result i64)))
  88. (f32 #'((result f64)))
  89. (f64 #'((result f64)))
  90. ((ref string) #'((result (ref eq))))
  91. ((ref extern) #'((result (ref eq))))
  92. ((ref null extern) #'((result (ref eq))))
  93. ((ref eq) #'((result (ref eq))))
  94. (type (%error "unsupported result type" #'type))))
  95. (define (lift-result exp)
  96. (syntax-case exp (none i32 i64 f32 f64 ref null string extern)
  97. ((x none) #'x)
  98. ((x i32) #'(i64.extend_i32_s x))
  99. ((x i64) #'x)
  100. ((x f32) #'(f64.promote_f32 x))
  101. ((x f64) #'x)
  102. ((x (ref string)) #'(struct.new $string (i32.const 0) x))
  103. ((x (ref extern)) #'(struct.new $extern-ref (i32.const 0) x))
  104. ((x (ref null extern)) #'(struct.new $extern-ref (i32.const 0) x))
  105. ((x (ref eq)) #'(ref.cast $heap-object x))
  106. (type (%error "unsupported result type" #'type))))
  107. (define (fresh-wasm-id prefix)
  108. (datum->syntax x (gensym prefix)))
  109. (define (fresh-wasm-ids prefix lst)
  110. (map (lambda (_) (fresh-wasm-id prefix)) lst))
  111. (syntax-case x (->)
  112. ((_ proc-name mod name ptype ... -> rtype)
  113. (with-syntax ((iname (fresh-wasm-id "$import-"))
  114. ((pname ...) (fresh-wasm-ids "$param-" #'(ptype ...))))
  115. #`(begin
  116. (%wasm-import
  117. '(func iname (import mod name)
  118. (param ptype) ...
  119. #,@(import-result-types #'rtype)))
  120. (define (proc-name pname ...)
  121. #,@(map (lambda (exp) (type-check exp #'proc-name))
  122. #'((pname ptype) ...))
  123. (%inline-wasm
  124. '(func (param pname ptype) ...
  125. #,@(result-types #'rtype)
  126. #,(lift-result
  127. #'((call iname (local.get pname) ...) rtype)))
  128. pname ...)))))))))