racket / typed-racket

Typed Racket
Other
527 stars 104 forks source link

any-wrap: avoid wrapping immutable structs with immutable fields #1333

Open bennn opened 1 year ago

bennn commented 1 year ago

When an immutable struct with immutable fields crosses an Any boundary, it shouldn't need a wrapper.

Example program (from #1332 ):

#lang typed/racket

(require/typed contract-profile
               (contract-profile-thunk (-> (-> Any) Any)))

(define-type Fighter Integer)
(define-type Pill Integer)
(struct interactive [{whose-turn : Symbol} {state : State}] #:prefab #:type-name Interactive)
(struct state [{fighters : [Listof Fighter]} {pills : {Listof Pill}}] #:prefab #:type-name State)

(define mk-state
  (let ((ff 0) (pp 0))
    (lambda ()
      (state
        (begin0 (list ff) (set! ff (+ 1 ff)))
        (begin0 (list pp) (set! pp (+ 1 pp)))))))

(define mk-interactive
  (let ((ww 'who))
    (lambda ()
      (interactive
        ww
        (mk-state)))))

(define NN (expt 10 4)) ;; higher value, slower runtime

(define ii*
  (for/list : (Listof Interactive) ((_kk : Natural (in-range NN)))
    (cast (cast (mk-interactive) Any) Interactive)))

(contract-profile-thunk
  (lambda ()
    (for ((ii (in-list ii*)))
      (define st (interactive-state ii))
      (+ (first (state-fighters st))
         (first (state-pills st))))))

The contract profile says 40% of the running time is contracts. But all the checks could happen outside the call to contract-profile-thunk.

For this to work, any-wrap.rkt will need to search through struct fields and the types inside.

(If Pill was a Boxof Integer, then the wrapper would be important.)

samth commented 1 year ago

Note that (a) substructures could add mutable fields (and TR's types don't currently track prop:sealed) and (b) functions are "mutable" in this sense.

mfelleisen commented 1 year ago

Understood. — Not unwrapping eagerly imposes a bad performance overhead (6x). We should try.

AlexKnauth commented 1 year ago

For any-wrap/c to avoid wrapping these, I don't see why TR's types not tracking prop:sealed should be relevant... any-wrap/c can use runtime reflection operations right?

The important thing would be making sure, at runtime, that a value known to be an instance of an immutable struct A, isn't actually an instance of a mutable struct B that inherits from A.

The 2nd return value of struct-info, skipped?, should be able to determine that, so it should only avoid wrapping it if skipped? is #false.