Search code examples
schemeffiapple-siliconchez-scheme

Chez Scheme FFI Procedure Doesn't Work After Change to Apple Silicon


I have been using a simple piece of FFI code in Chez Scheme to obtain the number of rows and columns of the terminal emulator where a program is running. The procedure used to work when running with the x86 version of Chez (9.5.8).

However, I have recently switched to an Apple Silicon-based development machine and the code no longer returns the correct answer. The version of Chez used on the Apple machine is 9.9.9-pre-release-20.

The same code loads and runs with no errors but always returns a size of (0 0) regardless of the actual size of the terminal window. I don't have access to an Intel-based machine to assure that some change didn't sneak in somewhere, but I don't see it and it doesn't appear different in any of the version-control repositories where the procedure is used.

Here's the code:

;;; win-size-demo.ss -- Procedure to return the size (in rows and columns)
;;; of the terminal it is running in.
(import (chezscheme))

;; Load the C runtime library. Only tested on macOS. The other clauses
;; were taken from examples in other files of the Chez Scheme
;; source distribution.
(case (machine-type)
  [(i3le ti3le a6le ta6le) (load-shared-object "libc.so.6")]
  [(i3osx ti3osx a6osx ta6osx) (load-shared-object "libc.dylib")]
  [(i3nt ti3nt a6nt ta6nt) (begin (load-shared-object "msvcrt.dll")
                                  (load-shared-object "kernel32.dll"))]
  ;; The following is a new addition for Apple Silicon systems.
  [(arm64osx tarm64osx) (load-shared-object "libSystem.dylib")]
  [else (load-shared-object "libc.so")])

;; The preferred way to interrogate the window size is through the
;; `ioctl` function from the underlying C implementation. Here's
;; how that (used to) works.

;; Define some file descriptors for stdin/out. Couldn't find this
;; documented anywhere. These values are from Chez expediter.c.
(define STDIN_FD 0)
(define STDOUT_FD 1)

;; Value from /Library/Developer/CommandLineTools/SDKs/MacOSX13.3.sdk/usr/includes/sys/ttycom.h.
;; This value is caclulated via a C macro in <sys/ioccom.h>.
(define TIOCGWINSZ #x40087468)

;; From <sys/ttycom.h>.
(define-ftype win-size
  (struct
    [ws-row unsigned-short] ; number of rows in the window, in characters
    [ws-col unsigned-short] ; number of columns in the window, in characters
    [ws-xpixel unsigned-short] ; horizontal size of the window, in pixels
    [ws-ypixel unsigned-short] ; vertical size of the window, in pixels
    ))

(define ioctl (foreign-procedure "ioctl" (int int (* win-size)) int))

(define errno (foreign-procedure "(cs)s_errno" () int))
(define strerror (foreign-procedure "(cs)s_strerror" (int) scheme-object))

;; Return the size of the terminal window using the `ioctl` function
;; from the underlying C library.
(define (window-size)
  (let* ((win-size-buf (foreign-alloc (ftype-sizeof win-size)))
         (win-size-ptr (make-ftype-pointer win-size win-size-buf)))
    (let ((the-size (dynamic-wind
                      (lambda () #f)
                      (lambda () (let ((ctl-result (ioctl STDIN_FD TIOCGWINSZ win-size-ptr)))
                                   (if (negative? ctl-result)
                                       (let ((cep (current-error-port)))
                                         (display "Error getting display size.\n" cep)
                                         (display (strerror (errno)) cep)
                                         (newline cep)
                                         (list -1 -1))
                                       (list (ftype-ref win-size (ws-row) win-size-ptr)
                                             (ftype-ref win-size (ws-col) win-size-ptr)))))
                      (lambda () (foreign-free win-size-buf)))))
      the-size)))

Reading the release notes for Chez Scheme 9.9.9, the section on changes to the FFI do not seem like they would make any difference. But obviously I'm missing something.


Solution

  • For Chez Scheme 9.9.9 and later, you must explicitly declare varargs functions like ioctl:

    (define ioctl (foreign-procedure (__varargs_after 2) "ioctl" (int int (* win-size)) int))

    Also, you can use libc.dylib for arm64osx and tarm64osx, so it won't require a separate line in the case statement.