"record.ss"
;;; Copyright 1984-2016 Cisco Systems, Inc.
;;; 
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
;;; You may obtain a copy of the License at
;;; 
;;; http://www.apache.org/licenses/LICENSE-2.0
;;; 
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.

;;; cp0 is needed to optimize away run-time calls to record-constructor,
;;; record-predicate, etc., in define-record-type for rcd.
(eval-when (compile) (run-cp0 (default-run-cp0)))

;;; TODO:
;;; indirect flag for $record{,-ref,-set!}
;;; gc support for indirect records
;;; examples/foreign.ss support for (indirect) records
;;; support for more datatypes
;;; SWIG converter?
;;; include size of tag in record size OR don't include tag in record offsets

(let ()
  (define (rtd-parent x) ($object-ref 'scheme-object x (constant record-type-parent-disp)))
  (define (rtd-size x) ($object-ref 'scheme-object x (constant record-type-size-disp)))
  (define (rtd-pm x) ($object-ref 'scheme-object x (constant record-type-pm-disp)))
  (define (rtd-mpm x) ($object-ref 'scheme-object x (constant record-type-mpm-disp)))
  (define (rtd-name x) ($object-ref 'scheme-object x (constant record-type-name-disp)))
  (define (rtd-flds x) ($object-ref 'scheme-object x (constant record-type-flds-disp)))
  (define (rtd-flags x) ($object-ref 'scheme-object x (constant record-type-flags-disp)))
  (define (rtd-uid x) ($object-ref 'scheme-object x (constant record-type-uid-disp)))

  (define (child-flds rtd)
    (let ([flds (rtd-flds rtd)] [prtd (rtd-parent rtd)])
      (if prtd
          (list-tail flds (length (rtd-flds prtd)))
          flds)))

  ; $record is hand-coded and is defined in prims.ss

  (let ([addr? (constant-case ptr-bits
                 [(32) $integer-32?]
                 [(64) $integer-64?])])
    (set-who! foreign-alloc
      (let ([malloc (foreign-procedure "(cs)malloc" (fixnum) uptr)])
        (lambda (n)
          (unless (and (fixnum? n) (fx> n 0))
            ($oops who "~s is not a positive fixnum" n))
          (let ([x (malloc n)])
            (if (string? x)
                ($oops who "system call failed: ~(~a~)" x)
                x)))))

    (set-who! foreign-free
      (let ([free (foreign-procedure "(cs)free" (uptr) void)])
        (lambda (addr)
          (unless (addr? addr) ($oops who "invalid foreign address ~s" addr))
          (free addr))))

    (let ()
      (define (check-args who ty addr offset)
        (define-syntax check-ending-addr
          (syntax-rules ()
            [(_ type bytes pred)
             (unless (addr? (+ addr offset (fx- bytes 1)))
               ($oops who "invalid effective address (+ ~s ~s) for ~s-byte type ~s" addr offset bytes 'type))]))
        (unless (addr? addr) ($oops who "invalid address ~s" addr))
        (unless (fixnum? offset) ($oops who "~s is not a fixnum" offset))
        (unless (addr? (+ addr offset)) ($oops who "invalid effective address (+ ~s ~s)" addr offset))
        (record-datatype cases (filter-foreign-type ty) check-ending-addr
          ($oops who "unrecognized type ~s" ty)))
      (set-who! foreign-ref ; checks ty, addr, and offset, but inherently unsafe
        (lambda (ty addr offset)
          (define-syntax ref
            (syntax-rules (scheme-object char wchar boolean integer-64 unsigned-64)
              [(_ scheme-object bytes pred) ($oops who "cannot load scheme pointers from foreign memory")]
              [(_ char bytes pred) (integer->char (#3%foreign-ref 'unsigned-8 addr offset))]
              [(_ wchar bytes pred)
               (constant-case wchar-bits
                 [(16) (integer->char (#3%foreign-ref 'unsigned-16 addr offset))]
                 [(32) (integer->char (#3%foreign-ref 'unsigned-32 addr offset))])]
              [(_ boolean bytes pred)
               (constant-case int-bits
                 [(32) (not (eq? (#3%foreign-ref 'integer-32 addr offset) 0))]
                 [(64) (not (eq? (#3%foreign-ref 'integer-64 addr offset) 0))])]
              [(_ integer-64 bytes pred)
               (< (constant ptr-bits) 64)
               (constant-case native-endianness
                 [(big)
                  (logor (ash (#3%foreign-ref 'integer-32 addr offset) 32)
                    (#3%foreign-ref 'unsigned-32 (+ addr 4) offset))]
                 [(little)
                  (logor (ash (#3%foreign-ref 'integer-32 (+ addr 4) offset) 32)
                    (#3%foreign-ref 'unsigned-32 addr offset))])]
              [(_ unsigned-64 bytes pred)
               (< (constant ptr-bits) 64)
               (constant-case native-endianness
                 [(big)
                  (logor (ash (#3%foreign-ref 'unsigned-32 addr offset) 32)
                    (#3%foreign-ref 'unsigned-32 (+ addr 4) offset))]
                 [(little)
                  (logor (ash (#3%foreign-ref 'unsigned-32 (+ addr 4) offset) 32)
                    (#3%foreign-ref 'unsigned-32 addr offset))])]
              [(_ type bytes pred) (#3%foreign-ref 'type addr offset)]))
          (check-args who ty addr offset)
          (record-datatype cases (filter-foreign-type ty) ref
            ($oops who "unrecognized type ~s" ty))))

      (set-who! foreign-set! ; checks ty, addr, offset, and v, but inherently unsafe
        (lambda (ty addr offset v)
          (define (value-err x t) ($oops who "invalid value ~s for foreign type ~s" x t))
          (define-syntax set
            (syntax-rules (scheme-object char wchar boolean integer-40 unsigned-40 integer-48 unsigned-48
                            integer-56 unsigned-56 integer-64 unsigned-64)
              [(_ scheme-object bytes pred) ($oops who "cannot store scheme pointers into foreign memory")]
              [(_ char bytes pred)
               (begin
                 (unless (pred v) (value-err v ty))
                 (#3%foreign-set! 'unsigned-8 addr offset (char->integer v)))]
              [(_ wchar bytes pred)
               (begin
                 (unless (pred v) (value-err v ty))
                 (constant-case wchar-bits
                   [(16) (#3%foreign-set! 'unsigned-16 addr offset (char->integer v))]
                   [(32) (#3%foreign-set! 'unsigned-32 addr offset (char->integer v))]))]
              [(_ boolean bytes pred)
               (constant-case int-bits
                 [(32) (#3%foreign-set! 'integer-32 addr offset (if v 1 0))]
                 [(64) (#3%foreign-set! 'integer-64 addr offset (if v 1 0))])]
              [(_ integer-40 bytes pred)
               (< (constant ptr-bits) 64)
               (begin
                 (unless (pred v) (value-err v ty))
                 (constant-case native-endianness
                   [(big)
                    (#3%foreign-set! 'integer-32 addr offset (bitwise-arithmetic-shift-right v 8))
                    (#3%foreign-set! 'unsigned-8 (+ addr 4) offset (logand v (- (expt 2 8) 1)))]
                   [(little)
                    (#3%foreign-set! 'unsigned-32 addr offset (logand v (- (expt 2 32) 1)))
                    (#3%foreign-set! 'integer-8 (+ addr 4) offset (bitwise-arithmetic-shift-right v 32))]))]
              [(_ unsigned-40 bytes pred)
               (< (constant ptr-bits) 64)
               (begin
                 (unless (pred v) (value-err v ty))
                 (constant-case native-endianness
                   [(big)
                    (#3%foreign-set! 'unsigned-32 addr offset (bitwise-arithmetic-shift-right v 8))
                    (#3%foreign-set! 'unsigned-8 (+ addr 4) offset (logand v (- (expt 2 8) 1)))]
                   [(little)
                    (#3%foreign-set! 'unsigned-32 addr offset (logand v (- (expt 2 32) 1)))
                    (#3%foreign-set! 'unsigned-8 (+ addr 4) offset (bitwise-arithmetic-shift-right v 32))]))]
              [(_ integer-48 bytes pred)
               (< (constant ptr-bits) 64)
               (begin
                 (unless (pred v) (value-err v ty))
                 (constant-case native-endianness
                   [(big)
                    (#3%foreign-set! 'integer-32 addr offset (bitwise-arithmetic-shift-right v 16))
                    (#3%foreign-set! 'unsigned-16 (+ addr 4) offset (logand v (- (expt 2 16) 1)))]
                   [(little)
                    (#3%foreign-set! 'unsigned-32 addr offset (logand v (- (expt 2 32) 1)))
                    (#3%foreign-set! 'integer-16 (+ addr 4) offset (bitwise-arithmetic-shift-right v 32))]))]
              [(_ unsigned-48 bytes pred)
               (< (constant ptr-bits) 64)
               (begin
                 (unless (pred v) (value-err v ty))
                 (constant-case native-endianness
                   [(big)
                    (#3%foreign-set! 'unsigned-32 addr offset (bitwise-arithmetic-shift-right v 16))
                    (#3%foreign-set! 'unsigned-16 (+ addr 4) offset (logand v (- (expt 2 16) 1)))]
                   [(little)
                    (#3%foreign-set! 'unsigned-32 addr offset (logand v (- (expt 2 32) 1)))
                    (#3%foreign-set! 'unsigned-16 (+ addr 4) offset (bitwise-arithmetic-shift-right v 32))]))]
              [(_ integer-56 bytes pred)
               (< (constant ptr-bits) 64)
               (begin
                 (unless (pred v) (value-err v ty))
                 (constant-case native-endianness
                   [(big)
                    (#3%foreign-set! 'integer-32 addr offset (bitwise-arithmetic-shift-right v 24))
                    (#3%foreign-set! 'unsigned-16 (+ addr 4) offset (logand (bitwise-arithmetic-shift-right v 8) (- (expt 2 16) 1)))
                    (#3%foreign-set! 'unsigned-8 (+ addr 6) offset (logand v (- (expt 2 8) 1)))]
                   [(little)
                    (#3%foreign-set! 'unsigned-32 addr offset (logand v (- (expt 2 32) 1)))
                    (#3%foreign-set! 'unsigned-16 (+ addr 4) offset (fxlogand (bitwise-arithmetic-shift-right v 32) (- (expt 2 16) 1)))
                    (#3%foreign-set! 'integer-8 (+ addr 6) offset (bitwise-arithmetic-shift-right v 48))]))]
              [(_ unsigned-56 bytes pred)
               (< (constant ptr-bits) 64)
               (begin
                 (unless (pred v) (value-err v ty))
                 (constant-case native-endianness
                   [(big)
                    (#3%foreign-set! 'unsigned-32 addr offset (bitwise-arithmetic-shift-right v 24))
                    (#3%foreign-set! 'unsigned-16 (+ addr 4) offset (logand (bitwise-arithmetic-shift-right v 8) (- (expt 2 16) 1)))
                    (#3%foreign-set! 'unsigned-8 (+ addr 6) offset (logand v (- (expt 2 8) 1)))]
                   [(little)
                    (#3%foreign-set! 'unsigned-32 addr offset (logand v (- (expt 2 32) 1)))
                    (#3%foreign-set! 'unsigned-16 (+ addr 4) offset (fxlogand (bitwise-arithmetic-shift-right v 32) (- (expt 2 16) 1)))
                    (#3%foreign-set! 'unsigned-8 (+ addr 6) offset (bitwise-arithmetic-shift-right v 48))]))]
              [(_ integer-64 bytes pred)
               (< (constant ptr-bits) 64)
               (begin
                 (unless (pred v) (value-err v ty))
                 (constant-case native-endianness
                   [(big)
                    (#3%foreign-set! 'integer-32 addr offset (ash v -32))
                    (#3%foreign-set! 'unsigned-32 (+ addr 4) offset (logand v (- (expt 2 32) 1)))]
                   [(little)
                    (#3%foreign-set! 'integer-32 (+ addr 4) offset (ash v -32))
                    (#3%foreign-set! 'unsigned-32 addr offset (logand v (- (expt 2 32) 1)))]))]
              [(_ unsigned-64 bytes pred)
               (< (constant ptr-bits) 64)
               (begin
                 (unless (pred v) (value-err v ty))
                 (constant-case native-endianness
                   [(big)
                    (#3%foreign-set! 'unsigned-32 addr offset (ash v -32))
                    (#3%foreign-set! 'unsigned-32 (+ addr 4) offset (logand v (- (expt 2 32) 1)))]
                   [(little)
                    (#3%foreign-set! 'unsigned-32 (+ addr 4) offset (ash v -32))
                    (#3%foreign-set! 'unsigned-32 addr offset (logand v (- (expt 2 32) 1)))]))]
              [(_ type bytes pred)
               (begin
                 (unless (pred v) (value-err v ty))
                 (#3%foreign-set! 'type addr offset v))]))
          (check-args who ty addr offset)
          (record-datatype cases (filter-foreign-type ty) set
            ($oops who "unrecognized type ~s" ty))))))

  (set-who! $filter-foreign-type
    ; version that filters using host-machine information
    (lambda (ty)
      (filter-foreign-type ty)))

  (set-who! $object-ref ; not safe, just handles non-constant types
    (lambda (ty r offset)
      (define-syntax ref
        (syntax-rules (char wchar boolean integer-64 unsigned-64)
          [(_ char bytes pred) (integer->char (#3%$object-ref 'unsigned-8 r offset))]
          [(_ wchar bytes pred)
           (constant-case wchar-bits
             [(16) (integer->char (#3%$object-ref 'unsigned-16 r offset))]
             [(32) (integer->char (#3%$object-ref 'unsigned-32 r offset))])]
          [(_ boolean bytes pred)
           (constant-case int-bits
             [(32) (not (eq? (#3%$object-ref 'integer-32 r offset) 0))]
             [(64) (not (eq? (#3%$object-ref 'integer-64 r offset) 0))])]
          [(_ type bytes pred) (#3%$object-ref 'type r offset)]))
      (record-datatype cases (filter-foreign-type ty) ref
        ($oops who "unrecognized type ~s" ty))))

  (set-who! $swap-object-ref ; not safe, just handles non-constant types
    (lambda (ty r offset)
      (define-syntax ref
        (syntax-rules (char wchar boolean integer-64 unsigned-64)
          [(_ char bytes pred) (integer->char (#3%$swap-object-ref 'unsigned-8 r offset))]
          [(_ wchar bytes pred)
           (constant-case wchar-bits
             [(16) (integer->char (#3%$swap-object-ref 'unsigned-16 r offset))]
             [(32) (integer->char (#3%$swap-object-ref 'unsigned-32 r offset))])]
          [(_ boolean bytes pred)
           (constant-case int-bits
             [(32) (not (eq? (#3%$swap-object-ref 'integer-32 r offset) 0))]
             [(64) (not (eq? (#3%$swap-object-ref 'integer-64 r offset) 0))])]
          [(_ type bytes pred) (#3%$swap-object-ref 'type r offset)]))
      (record-datatype cases (filter-foreign-type ty) ref
        ($oops who "unrecognized type ~s" ty))))

  (set-who! $object-set! ; not safe, just handles non-constant types
    (lambda (ty r offset v)
      (define-syntax set
        (syntax-rules (char wchar boolean integer-40 unsigned-40 integer-48 unsigned-48
                       integer-56 unsigned-56 integer-64 unsigned-64)
          [(_ char bytes pred)
           (#3%$object-set! 'unsigned-8 r offset (char->integer v))]
          [(_ wchar bytes pred)
           (constant-case wchar-bits
             [(16) (#3%$object-set! 'unsigned-16 r offset (char->integer v))]
             [(32) (#3%$object-set! 'unsigned-32 r offset (char->integer v))])]
          [(_ boolean bytes pred)
           (constant-case int-bits
             [(32) (#3%$object-set! 'integer-32 r offset (if v 1 0))]
             [(64) (#3%$object-set! 'integer-64 r offset (if v 1 0))])]
          [(_ integer-40 bytes pred)
           (< (constant ptr-bits) 64)
           (begin
             (constant-case native-endianness
               [(big)
                (#3%$object-set! 'integer-32 r offset (bitwise-arithmetic-shift-right v 8))
                (#3%$object-set! 'unsigned-8 r (fx+ offset 4) (logand v (- (expt 2 8) 1)))]
               [(little)
                (#3%$object-set! 'unsigned-32 r offset (logand v (- (expt 2 32) 1)))
                (#3%$object-set! 'integer-8 r (fx+ offset 4) (bitwise-arithmetic-shift-right v 32))]))]
          [(_ unsigned-40 bytes pred)
           (< (constant ptr-bits) 64)
           (begin
             (constant-case native-endianness
               [(big)
                (#3%$object-set! 'unsigned-32 r offset (bitwise-arithmetic-shift-right v 8))
                (#3%$object-set! 'unsigned-8 r (fx+ offset 4) (logand v (- (expt 2 8) 1)))]
               [(little)
                (#3%$object-set! 'unsigned-32 r offset (logand v (- (expt 2 32) 1)))
                (#3%$object-set! 'unsigned-8 r (fx+ offset 4) (bitwise-arithmetic-shift-right v 32))]))]
          [(_ integer-48 bytes pred)
           (< (constant ptr-bits) 64)
           (begin
             (constant-case native-endianness
               [(big)
                (#3%$object-set! 'integer-32 r offset (bitwise-arithmetic-shift-right v 16))
                (#3%$object-set! 'unsigned-16 r (fx+ offset 4) (logand v (- (expt 2 16) 1)))]
               [(little)
                (#3%$object-set! 'unsigned-32 r offset (logand v (- (expt 2 32) 1)))
                (#3%$object-set! 'integer-16 r (fx+ offset 4) (bitwise-arithmetic-shift-right v 32))]))]
          [(_ unsigned-48 bytes pred)
           (< (constant ptr-bits) 64)
           (begin
             (constant-case native-endianness
               [(big)
                (#3%$object-set! 'unsigned-32 r offset (bitwise-arithmetic-shift-right v 16))
                (#3%$object-set! 'unsigned-16 r (fx+ offset 4) (logand v (- (expt 2 16) 1)))]
               [(little)
                (#3%$object-set! 'unsigned-32 r offset (logand v (- (expt 2 32) 1)))
                (#3%$object-set! 'unsigned-16 r (fx+ offset 4) (bitwise-arithmetic-shift-right v 32))]))]
          [(_ integer-56 bytes pred)
           (< (constant ptr-bits) 64)
           (begin
             (constant-case native-endianness
               [(big)
                (#3%$object-set! 'integer-32 r offset (bitwise-arithmetic-shift-right v 24))
                (#3%$object-set! 'unsigned-16 r (fx+ offset 4) (logand (bitwise-arithmetic-shift-right v 8) (- (expt 2 16) 1)))
                (#3%$object-set! 'unsigned-8 r (fx+ offset 6) (logand v (- (expt 2 8) 1)))]
               [(little)
                (#3%$object-set! 'unsigned-32 r offset (logand v (- (expt 2 32) 1)))
                (#3%$object-set! 'unsigned-16 r (fx+ offset 4) (fxlogand (bitwise-arithmetic-shift-right v 32) (- (expt 2 16) 1)))
                (#3%$object-set! 'integer-8 r (fx+ offset 6) (bitwise-arithmetic-shift-right v 48))]))]
          [(_ unsigned-56 bytes pred)
           (< (constant ptr-bits) 64)
           (begin
             (constant-case native-endianness
               [(big)
                (#3%$object-set! 'unsigned-32 r offset (bitwise-arithmetic-shift-right v 24))
                (#3%$object-set! 'unsigned-16 r (fx+ offset 4) (logand (bitwise-arithmetic-shift-right v 8) (- (expt 2 16) 1)))
                (#3%$object-set! 'unsigned-8 r (fx+ offset 6) (logand v (- (expt 2 8) 1)))]
               [(little)
                (#3%$object-set! 'unsigned-32 r offset (logand v (- (expt 2 32) 1)))
                (#3%$object-set! 'unsigned-16 r (fx+ offset 4) (fxlogand (bitwise-arithmetic-shift-right v 32) (- (expt 2 16) 1)))
                (#3%$object-set! 'unsigned-8 r (fx+ offset 6) (bitwise-arithmetic-shift-right v 48))]))]
          [(_ integer-64 bytes pred)
           (< (constant ptr-bits) 64)
           (constant-case native-endianness
             [(big)
              (#3%$object-set! 'integer-32 r offset (bitwise-arithmetic-shift-right v 32))
              (#3%$object-set! 'unsigned-32 r (fx+ offset 4) (logand v (- (expt 2 32) 1)))]
             [(little)
              (#3%$object-set! 'unsigned-32 r offset (logand v (- (expt 2 32) 1)))
              (#3%$object-set! 'integer-32 r (fx+ offset 4) (bitwise-arithmetic-shift-right v 32))])]
          [(_ unsigned-64 bytes pred)
           (< (constant ptr-bits) 64)
           (constant-case native-endianness
             [(big)
              (#3%$object-set! 'unsigned-32 r offset (bitwise-arithmetic-shift-right v 32))
              (#3%$object-set! 'unsigned-32 r (fx+ offset 4) (logand v (- (expt 2 32) 1)))]
             [(little)
              (#3%$object-set! 'unsigned-32 r offset (logand v (- (expt 2 32) 1)))
              (#3%$object-set! 'unsigned-32 r (fx+ offset 4) (bitwise-arithmetic-shift-right v 32))])]
          [(_ type bytes pred) (#3%$object-set! 'type r offset v)]))
      (record-datatype cases (filter-foreign-type ty) set
        ($oops who "unrecognized type ~s" ty))))

  (set-who! foreign-sizeof
    (lambda (ty)
      (define-syntax size
        (syntax-rules ()
          [(_ type bytes pred) bytes]))
      (record-datatype cases (filter-foreign-type ty) size
        ($oops who "invalid foreign type specifier ~s" ty))))

  (set-who! #(csv7: record-type-descriptor)
    (lambda (r)
      (unless (record? r) ($oops who "~s is not a record" r))
      (#3%record-rtd r)))

  (set-who! record-rtd
    (lambda (r)
      (unless (record? r) ($oops who "~s is not a record" r))
      (#3%record-rtd r)))

  (set! record-predicate
    (lambda (rtd)
      (unless (record-type-descriptor? rtd)
        ($oops 'record-predicate "~s is not a record type descriptor" rtd))
      (if (record-type-sealed? rtd)
          (rec predicate (lambda (x) ($sealed-record? x rtd)))
          (rec predicate (lambda (x) (record? x rtd))))))

  (let ((base-rtd #!base-rtd))
    (define (make-flags uid sealed? opaque? parent)
      (fxlogor
        (if uid 0 (constant rtd-generative))
        (if (or opaque? (and parent (record-type-opaque? parent)))
            (constant rtd-opaque)
            0)
        (if sealed? (constant rtd-sealed) 0)))
    (define ($mrt who base-rtd name parent uid flags fields extras)
      (include "layout.ss")
      (when (and parent (record-type-sealed? parent))
        ($oops who "cannot extend sealed record type ~s" parent))
      (let ([parent-fields (if (not parent) '() (csv7:record-type-field-decls parent))]
            [uid (or uid (gensym (symbol->string name)))])
       ; start base offset at rtd field
       ; synchronize with syntax.ss and front.ss
        (let-values ([(pm mpm flds size)
                      (compute-field-offsets who
                        (constant record-type-disp)
                       ; rtd must be immutable if we are ever to store records
                       ; in space pure
                        (cons `(immutable scheme-object ,uid)
                              (append parent-fields fields)))])
          (cond
            [(and (not (fxlogtest flags (constant rtd-generative)))
                  (let ([x ($sgetprop uid '*rtd* #f)])
                    (and (record-type-descriptor? x) x))) =>
             (lambda (rtd)
               (define same-fields?
                 (lambda (flds1 flds2)
                   (define same-field?
                     (lambda (fld1 fld2) ; mutability checked separately
                       (and (eq? (fld-name fld1) (fld-name fld2))
                           ; not using filter-foreign-type here.  this makes the
                           ; comparison faster and prevents unwanted machine-dependent
                           ; matches like int and integer-32.  it also prevents
                           ; ptr and scheme-object from matching---c'est la vie.
                            (eq? (fld-type fld1) (fld-type fld2))
                           ; following is paranoid; overall size
                           ; check should suffice
                            #;(= (fld-byte fld1) (fld-byte fld2)))))
                   (and (= (length flds1) (length flds2))
                        (andmap same-field? flds1 flds2))))
              ; following assumes extras match
               (let ()
                 (define (squawk what) ($oops who "incompatible record type ~s - ~a" name what))
                 (unless (eq? ($record-type-descriptor rtd) base-rtd) (squawk "different base rtd"))
                 (unless (eq? (rtd-parent rtd) parent) (squawk "different parent"))
                 (unless (same-fields? (rtd-flds rtd) (cdr flds)) (squawk "different fields"))
                 (unless (= (rtd-mpm rtd) mpm) (squawk "different mutability"))
                 (unless (fx= (rtd-flags rtd) flags) (squawk "different flags"))
                 (unless (eq? (rtd-size rtd) size) (squawk "different size")))
               rtd)]
            [else
             (let ([rtd (apply #%$record base-rtd parent size pm mpm name
                          (cdr flds) flags uid #f extras)])
               ($sputprop uid '*rtd* rtd)
               rtd)]))))

    (set-who! $remake-rtd
      (lambda (rtd compute-field-offsets)
        (let ([key ($target-machine)] [uid (rtd-uid rtd)])
          (assert (not (eq? key (machine-type))))
          (or ($sgetprop uid key #f)
              (let ([base-rtd ($record-type-descriptor rtd)]
                    [parent (rtd-parent rtd)]
                    [name (rtd-name rtd)]
                    [flags (rtd-flags rtd)]
                    [fields (csv7:record-type-field-decls rtd)])
                (let-values ([(pm mpm flds size)
                              (compute-field-offsets who
                                (constant record-type-disp)
                                (cons `(immutable scheme-object ,uid) fields))])
                  (let ([rtd (apply #%$record base-rtd parent size pm mpm name (cdr flds) flags uid #f
                               (let* ([n (length (rtd-flds ($record-type-descriptor base-rtd)))]
                                      [ls (list-tail (rtd-flds base-rtd) n)])
                                 (let f ([n n] [ls ls])
                                   (if (null? ls)
                                       '()
                                       (cons ((csv7:record-field-accessor base-rtd n) rtd)
                                         (f (fx+ n 1) (cdr ls)))))))])
                    ($sputprop uid key rtd)
                    rtd)))))))

    (let ()
      (define (mrt base-rtd parent name fields sealed? opaque? extras)
        (cond
          [(gensym? name)
           ($mrt 'make-record-type base-rtd
             (string->symbol (symbol->string name)) parent name
             (make-flags name sealed? opaque? parent)
             fields extras)]
          [(string? name)
           ($mrt 'make-record-type base-rtd
             (string->symbol name) parent #f
             (make-flags #f sealed? opaque? parent)
             fields extras)]
          [else ($oops 'make-record-type "invalid record name ~s" name)]))

      (set-who! make-record-type
        (rec make-record-type
          (case-lambda
            [(name fields)
             (unless (list? fields)
               ($oops who "invalid field list ~s" fields))
             (mrt base-rtd #f name fields #f #f '())]
            [(parent name fields)
             (unless (or (not parent) (record-type-descriptor? parent))
               ($oops who "~s is not a record type descriptor"
                 parent))
             (unless (list? fields)
               ($oops who "invalid field list ~s" fields))
             (mrt base-rtd parent name fields #f #f '())])))

      (set! $make-record-type
        (lambda (base-rtd parent name fields sealed? opaque? . extras)
          (unless (record-type-descriptor? base-rtd)
            ($oops 'make-record-type "~s is not a record type descriptor"
              base-rtd))
          (unless (or (not parent) (record-type-descriptor? parent))
            ($oops 'make-record-type "~s is not a record type descriptor"
              parent))
          (unless (list? fields)
            ($oops 'make-record-type "invalid field list ~s" fields))
          (mrt base-rtd parent name fields sealed? opaque? extras))))

    (let ()
      (define (mrtd base-rtd name parent uid sealed? opaque? fields who extras)
        (unless (symbol? name)
          ($oops who "invalid record name ~s" name))
        (unless (or (not parent) (record-type-descriptor? parent))
          ($oops who "invalid parent ~s" parent))
        (unless (or (not uid) (symbol? uid))
          ($oops who "invalid uid ~s" uid))
        (unless (vector? fields)
          ($oops who "invalid field vector ~s" fields))
        ($mrt who base-rtd name parent uid
          (make-flags uid sealed? opaque? parent)
          (let ([n (vector-length fields)])
            (let f ([i 0])
              (if (fx= i n)
                  '()
                  (let ([x (vector-ref fields i)])
                    (unless (and (pair? x)
                                 (memq (car x) '(mutable immutable))
                                 (let ([x (cdr x)])
                                   (and (pair? x)
                                        (symbol? (car x))
                                        (null? (cdr x)))))
                      ($oops who "invalid field specifier ~s" x))
                    (cons x (f (fx+ i 1)))))))
          extras))

      (set! $make-record-type-descriptor
        (lambda (base-rtd name parent uid sealed? opaque? fields who . extras)
          (unless (record-type-descriptor? base-rtd)
            ($oops who "invalid base rtd ~s" base-rtd))
          (mrtd base-rtd name parent uid sealed? opaque? fields who extras)))

      (set-who! make-record-type-descriptor
        (lambda (name parent uid sealed? opaque? fields)
          (mrtd base-rtd name parent uid sealed? opaque? fields who '()))))

    (set! record-type-descriptor?
      (lambda (x)
        (#3%record? x base-rtd)))

    (set! record?
      (case-lambda
        [(x) (#3%record? x)]
        [(x rtd)
         (unless (#3%record? rtd base-rtd)
           ($oops 'record? "~s is not a record type descriptor" rtd))
         (#3%record? x rtd)])))

  (set! r6rs:record?
    (rec record?
      (lambda (x)
        (#3%r6rs:record? x))))

  (set! record-type-parent
    (lambda (rtd)
      (unless (record-type-descriptor? rtd)
        ($oops 'record-type-parent "~s is not a record type descriptor" rtd))
      (rtd-parent rtd)))

  (set-who! #(csv7: record-type-name)
    (lambda (rtd)
      (unless (record-type-descriptor? rtd)
        ($oops who "~s is not a record type descriptor" rtd))
      (symbol->string (rtd-name rtd))))

  (set-who! record-type-name
    (lambda (rtd)
      (unless (record-type-descriptor? rtd)
        ($oops who "~s is not a record type descriptor" rtd))
      (rtd-name rtd)))

  (set-who! #(csv7: record-type-symbol)
    (lambda (rtd)
      (unless (record-type-descriptor? rtd)
        ($oops who "~s is not a record type descriptor" rtd))
      (rtd-uid rtd)))

  (set-who! record-type-uid
    (lambda (rtd)
      (unless (record-type-descriptor? rtd)
        ($oops who "~s is not a record type descriptor" rtd))
      (rtd-uid rtd)))

  (set-who! #(csv7: record-type-field-names)
    (lambda (rtd)
      (unless (record-type-descriptor? rtd)
        ($oops who "~s is not a record type descriptor" rtd))
      (map (lambda (x) (fld-name x)) (rtd-flds rtd))))

  (set-who! record-type-field-names
    (lambda (rtd)
      (unless (record-type-descriptor? rtd)
        ($oops who "~s is not a record type descriptor" rtd))
      (list->vector (map (lambda (x) (fld-name x)) (child-flds rtd)))))

  (set-who! #(csv7: record-type-field-decls)
    (lambda (rtd)
      (unless (record-type-descriptor? rtd)
        ($oops who "~s is not a record type descriptor" rtd))
      (map (lambda (x)
             `(,(if (fld-mutable? x) 'mutable 'immutable)
                ,(fld-type x)
                ,(fld-name x)))
           (rtd-flds rtd))))

  (set! $record-type-field-offsets
    (lambda (rtd)
      (unless (record-type-descriptor? rtd)
        ($oops '$record-type-field-offsets "~s is not a record type descriptor" rtd))
      (map (lambda (x) (fld-byte x)) (rtd-flds rtd))))

  (set! record-type-opaque?
    (lambda (rtd)
      (unless (record-type-descriptor? rtd)
        ($oops 'record-type-opaque? "~s is not a record type descriptor" rtd))
      (#3%record-type-opaque? rtd)))

  (set! record-type-sealed?
    (lambda (rtd)
      (unless (record-type-descriptor? rtd)
        ($oops 'record-type-sealed? "~s is not a record type descriptor" rtd))
      (#3%record-type-sealed? rtd)))

  (set! record-type-generative?
    (lambda (rtd)
      (unless (record-type-descriptor? rtd)
        ($oops 'record-type-generative? "~s is not a record type descriptor" rtd))
      (#3%record-type-generative? rtd)))

  (let ()
    (define (find-fld who rtd field-spec)
      (unless (record-type-descriptor? rtd)
        ($oops who "~s is not a record type descriptor" rtd))
      (cond
        [(symbol? field-spec)
        ; reverse order to check child's fields first
         (let loop ((flds (reverse (rtd-flds rtd))))
           (when (null? flds)
             ($oops who "unrecognized field name ~s for type ~s"
               field-spec rtd))
           (let ((fld (car flds)))
             (if (eq? field-spec (fld-name fld))
                 fld
                 (loop (cdr flds)))))]
        [(and (fixnum? field-spec) (fx>= field-spec 0))
         (let ((flds (rtd-flds rtd)))
           (when (fx>= field-spec (length flds))
             ($oops who "invalid field ordinal ~s for type ~s"
               field-spec rtd))
           (list-ref flds field-spec))]
        [else ($oops who "invalid field specifier ~s" field-spec)]))

    (define (r6rs:find-fld who rtd field-spec)
      (unless (record-type-descriptor? rtd)
        ($oops who "~s is not a record type descriptor" rtd))
      (cond
        [(and (fixnum? field-spec) (fx>= field-spec 0))
         (let ((flds (child-flds rtd)))
           (when (fx>= field-spec (length flds))
             ($oops who "invalid field index ~s for type ~s"
               field-spec rtd))
           (list-ref flds field-spec))]
        [else ($oops who "invalid field specifier ~s" field-spec)]))

    (let ()
      (define (rfa who rtd fld)
        (let ((record-err (lambda (x) ($record-oops #f x rtd)))
              (offset (fld-byte fld))
              (ty (fld-type fld)))
          (define-syntax ref
            (syntax-rules ()
              [(_ type bytes pred)
               (rec accessor
                 (lambda (x)
                   (unless (record? x rtd) (record-err x))
                   (#3%$object-ref 'type x offset)))]))
          (record-datatype cases (filter-foreign-type ty) ref
            ($oops who "unrecognized type ~s" ty))))
      (set-who! #(csv7: record-field-accessor)
        (lambda (rtd field-spec)
          (rfa who rtd (find-fld who rtd field-spec))))
      (set-who! record-accessor
        (lambda (rtd field-spec)
          (rfa who rtd (r6rs:find-fld who rtd field-spec)))))

    (let ()
      (define (rfm who rtd fld field-spec)
        (if (fld-mutable? fld)
            (let ((record-err (lambda (x t) ($record-oops #f x t)))
                  (value-err (lambda (x t) ($oops #f "invalid value ~s for foreign type ~s" x t)))
                  (offset (fld-byte fld))
                  (ty (fld-type fld)))
              (define-syntax set
                (syntax-rules (scheme-object)
                  [(_ scheme-object bytes pred)
                   (rec mutator
                     (lambda (x v)
                       (unless (record? x rtd) (record-err x rtd))
                       (#3%$object-set! 'scheme-object x offset v)))]
                  [(_ type bytes pred)
                   (rec mutator
                     (lambda (x v)
                       (unless (record? x rtd) (record-err x rtd))
                       (unless (pred v) (value-err v ty))
                       (#3%$object-set! 'type x offset v)))]))
              (record-datatype cases (filter-foreign-type ty) set
                ($oops who "unrecognized type ~s" ty)))
            ($oops who "field ~s of ~s is immutable"
              field-spec rtd)))
      (set-who! #(csv7: record-field-mutator)
        (lambda (rtd field-spec)
          (rfm who rtd (find-fld who rtd field-spec) field-spec)))
      (set-who! record-mutator
        (lambda (rtd field-spec)
          (rfm who rtd (r6rs:find-fld who rtd field-spec) field-spec))))

    (set-who! #(csv7: record-field-accessible?)
     ; if this is ever made to do anything reasonable, revisit handlers in
     ; cp0 and cp1in as well
      (lambda (rtd field-spec)
        (find-fld who rtd field-spec)
        #t))

    (set-who! #(csv7: record-field-mutable?)
      (lambda (rtd field-spec)
        (fld-mutable? (find-fld who rtd field-spec))))

    (set-who! record-field-mutable?
      (lambda (rtd field-spec)
        (fld-mutable? (r6rs:find-fld who rtd field-spec)))))

  (let ()
   ; if you update this, also update duplicate in cp0.ss
    (define-record-type rcd
      (fields (immutable rtd) (immutable prcd) (immutable protocol))
      (nongenerative #{rcd qh0yzh5qyrxmz2l-a})
      (sealed #t))

    (set! record-constructor-descriptor?
      (lambda (x)
        (rcd? x)))

    (let ()
      (define (mrcd rtd prcd protocol who)
        (unless (record-type-descriptor? rtd)
          ($oops who "~s is not a record-type descriptor" rtd))
        (unless (or (not prcd) (rcd? prcd))
          ($oops who "invalid record constructor descriptor ~s" prcd))
        (unless (or (not protocol) (procedure? protocol))
          ($oops who "invalid protocol ~s" protocol))
        (unless (eqv? (rtd-pm rtd) -1) ; all pointers?
          ($oops who "cannot create constructor descriptor for record type with non-scheme-object fields"))
        (let ([prtd (record-type-parent rtd)])
          (when (and prcd (not prtd))
            ($oops who
              "record constructor descriptor ~s specified for base record type ~s"
              prcd rtd))
          (when (and prcd prtd (not (eq? (rcd-rtd prcd) prtd)))
            ($oops who
              "record constructor descriptor ~s is not for parent of record type ~s"
              prcd rtd))
          (when (and (not protocol) prcd (rcd-protocol prcd))
            ($oops who "no protocol specified, but parent ~s has protocol" prcd))
          (make-rcd rtd prcd protocol)))

      (set! $make-record-constructor-descriptor
        (lambda (rtd prcd protocol who)
          (mrcd rtd prcd protocol who)))

      (set! make-record-constructor-descriptor
        (lambda (rtd prcd protocol)
          (mrcd rtd prcd protocol 'make-record-constructor-descriptor))))

    (let ()
      (define $rtd->record-constructor
        (lambda (rtd)
          (define type->pred
            (lambda (ty)
              (define-syntax ->pred
                (syntax-rules () ((_ type bytes pred) 'pred)))
              (record-datatype cases ty ->pred
                ($oops 'record-constructor "unrecognized type ~s" ty))))
          (let* ((flds (rtd-flds rtd)) (nflds (length flds)))
            (if (eqv? (rtd-pm rtd) -1) ; all pointers?
                (let ()
                  (define-syntax nlambda
                    (lambda (x)
                      (syntax-case x ()
                        [(_ n)
                         (with-syntax (((t ...)
                                        (generate-temporaries
                                          (make-list
                                            (datum n)))))
                           #'(rec constructor
                               (lambda (t ...) ($record rtd t ...))))])))
                  (case nflds
                    [(0) (nlambda 0)]
                    [(1) (nlambda 1)]
                    [(2) (nlambda 2)]
                    [(3) (nlambda 3)]
                    [(4) (nlambda 4)]
                    [(5) (nlambda 5)]
                    [(6) (nlambda 6)]
                    [else (rec constructor
                            (lambda xr
                              (unless (fx= (length xr) nflds)
                                ($oops #f "incorrect number of arguments to ~s" constructor))
                              (apply $record rtd xr)))]))
                (let* ([args (make-record-call-args flds (rtd-size rtd)
                               (map (lambda (x) 0) flds))]
                       [nargs (length args)]
                       [setters (map (lambda (fld)
                                       (let ([byte (fld-byte fld)]
                                             [ty (fld-type fld)])
                                         (let ([msg (format "invalid value ~~s for foreign type ~s" ty)])
                                           (define-syntax init
                                             (syntax-rules (scheme-object)
                                               [(_ scheme-object bytes pred)
                                                (lambda (x v)
                                                  (#3%$object-set! 'scheme-object x byte v))]
                                               [(_ type bytes pred)
                                                (lambda (x v)
                                                  (unless (pred v) ($oops #f msg v))
                                                  (#3%$object-set! 'type x byte v))]))
                                           (record-datatype cases (filter-foreign-type ty) init
                                             ($oops 'record-constructor "unrecognized type ~s" ty)))))
                                  flds)])
                  (define-syntax nmlambda
                    (lambda (x)
                      (syntax-case x ()
                        [(_ n m)
                         (with-syntax ([(t ...) (generate-temporaries
                                                  (make-list (datum n)))]
                                       [(z ...) (make-list (datum m) 0)])
                           (with-syntax ([(t! ...) (generate-temporaries #'(t ...))])
                             #'(apply
                                 (lambda (t! ...)
                                   (rec constructor
                                     (lambda (t ...)
                                       (let ([x ($record rtd z ...)])
                                         (t! x t) ...
                                         x))))
                                 setters)))])))
                  (or (constant-case ptr-bits
                        [(64)
                         (case nflds
                           [(0) (and (= nargs 0) (nmlambda 0 0))]
                           [(1) (and (= nargs 1) (nmlambda 1 1))]
                           [(2) (case nargs
                                  [(1) (nmlambda 2 1)]
                                  [(2) (nmlambda 2 2)]
                                  [else #f])]
                           [(3) (case nargs
                                  [(1) (nmlambda 3 1)]
                                  [(2) (nmlambda 3 2)]
                                  [(3) (nmlambda 3 3)]
                                  [else #f])]
                           [(4) (case nargs
                                  [(1) (nmlambda 4 1)]
                                  [(2) (nmlambda 4 2)]
                                  [(3) (nmlambda 4 3)]
                                  [(4) (nmlambda 4 4)]
                                  [else #f])]
                           [else #f])]
                        [(32)
                         (case nflds
                           [(0) (nmlambda 0 0)]
                           [(1) (case nargs
                                  [(1) (nmlambda 1 1)]
                                  [(2) (nmlambda 1 2)]
                                  [(3) (nmlambda 1 3)]
                                  [else #f])]
                           [(2) (case nargs
                                  [(1) (nmlambda 2 1)]
                                  [(2) (nmlambda 2 2)]
                                  [(3) (nmlambda 2 3)]
                                  [(4) (nmlambda 2 4)]
                                  [(5) (nmlambda 2 5)]
                                  [else #f])]
                           [(3) (case nargs
                                  [(1) (nmlambda 3 1)]
                                  [(2) (nmlambda 3 2)]
                                  [(3) (nmlambda 3 3)]
                                  [(4) (nmlambda 3 4)]
                                  [(5) (nmlambda 3 5)]
                                  [(6) (nmlambda 3 6)]
                                  [(7) (nmlambda 3 7)]
                                  [else #f])]
                           [(4) (case nargs
                                  [(1) (nmlambda 4 1)]
                                  [(2) (nmlambda 4 2)]
                                  [(3) (nmlambda 4 3)]
                                  [(4) (nmlambda 4 4)]
                                  [(5) (nmlambda 4 5)]
                                  [(6) (nmlambda 4 6)]
                                  [(7) (nmlambda 4 7)]
                                  [(8) (nmlambda 4 8)]
                                  [(9) (nmlambda 4 9)]
                                  [else #f])]
                           [else #f])])
                     (rec constructor
                       (lambda xr
                         (unless (fx= (length xr) nflds)
                           ($oops #f "incorrect number of arguments to ~s" constructor))
                         (let ([x (apply $record rtd args)])
                           (for-each (lambda (setter v) (setter x v)) setters xr)
                           x)))))))))

      (define ($rcd->record-constructor rcd)
        (let ([rtd (rcd-rtd rcd)] [protocol (rcd-protocol rcd)])
          (let ([rc ($rtd->record-constructor rtd)])
            (if protocol
                (protocol
                  (cond
                    [(rtd-parent rtd) =>
                     (lambda (prtd)
                       (lambda pp-args
                         (lambda vals
                           (let f ([prcd (rcd-prcd rcd)] [prtd prtd] [pp-args pp-args] [vals vals])
                             (#2%apply
                               (cond
                                 [(and prcd (rcd-protocol prcd)) =>
                                  (lambda (protocol)
                                    (protocol
                                      (cond
                                        [(rtd-parent prtd) =>
                                         (lambda (prtd)
                                           (lambda pp-args
                                             (lambda new-vals
                                               (f (rcd-prcd prcd) prtd pp-args
                                                  (append new-vals vals)))))]
                                        [else
                                         (lambda new-vals
                                           (apply rc (append new-vals vals)))])))]
                                 [else
                                  (lambda new-vals
                                    (apply rc (append new-vals vals)))])
                               pp-args)))))]
                    [else rc]))
                rc))))

      (set! record-constructor
        (lambda (x)
          (cond
            [(record-type-descriptor? x) ($rtd->record-constructor x)]
            [(record-constructor-descriptor? x) ($rcd->record-constructor x)]
            [else ($oops 'record-constructor "~s is not a record type or constructor descriptor" x)])))

      (set-who! #(r6rs: record-constructor)
        (lambda (rcd)
          (unless (rcd? rcd)
            ($oops who "~s is not a record constructor descriptor" rcd))
          ($rcd->record-constructor rcd)))))
)
