Skip to content

Commit 9df037b

Browse files
committed
static-contracts: less or/sc optimization
Prevent the static contract optimizer from changing constructors under `or/sc`. i.e., for static contracts of the form `(or/sc other-scs ...)`, the optimizer cannot optimize any of the `other-scs ...` to `any/sc` but it can optimize sub-contracts of the `other-scs ...` Example: `(or/sc set?/sc (box/sc set?/sc))` in a trusted position now optimizes to itself, instead of `any/sc` Optimization can resume under a sub-contract that represents a "heavy" type constructor. (I mean, `U` is a type constructor but it's not "heavy" like that.)
1 parent ff1446f commit 9df037b

File tree

3 files changed

+155
-13
lines changed

3 files changed

+155
-13
lines changed

typed-racket-lib/typed-racket/static-contracts/optimize.rkt

Lines changed: 98 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,23 +117,106 @@
117117
[(arr/sc: args rest (list (any/sc:) ...))
118118
(arr/sc args rest #f)]
119119
[(none/sc:) any/sc]
120-
[(app sc-terminal-kind 'flat) any/sc]
120+
[(or/sc: (? flat-terminal-kind?) ...) any/sc]
121+
[(? flat-terminal-kind?) any/sc]
121122
[else sc]))
122123

124+
(define (flat-terminal-kind? sc)
125+
(eq? 'flat (sc-terminal-kind sc)))
123126

127+
;; The side of a static contract describes the source of the values that
128+
;; the contract needs to check.
129+
;; - 'positive : values exported by the server module
130+
;; - 'negative : values imported from a client module
131+
;; - 'both : values from both server & client
132+
(define (side? v)
133+
(memq v '(positive negative both)))
134+
135+
;; A _weak side_ is a side that is currently unsafe to optimize
136+
;; Example:
137+
;; when optimizing an `(or/sc scs ...)` on the 'positive side,
138+
;; each of the `scs` should be optimized on the '(weak positive) side,
139+
;; and their sub-contracts --- if any --- may be optimized on the 'positive side
140+
(define (weak-side? x)
141+
(match x
142+
[(list 'weak (? side?))
143+
#true]
144+
[_
145+
#false]))
146+
147+
(define (strengthen-side side)
148+
(if (weak-side? side)
149+
(second side)
150+
side))
151+
152+
(define (weaken-side side)
153+
(if (weak-side? side)
154+
side
155+
`(weak ,side)))
124156

125157
(define (invert-side v)
126-
(case v
127-
[(positive) 'negative]
128-
[(negative) 'positive]
129-
[(both) 'both]))
158+
(if (weak-side? v)
159+
(weaken-side (invert-side v))
160+
(case v
161+
[(positive) 'negative]
162+
[(negative) 'positive]
163+
[(both) 'both])))
130164

131165
(define (combine-variance side var)
132166
(case var
133167
[(covariant) side]
134168
[(contravariant) (invert-side side)]
135169
[(invariant) 'both]))
136170

171+
;; update-side : sc? weak-side? -> weak-side?
172+
;; Change the current side to something safe & strong-as-possible
173+
;; for optimizing the sub-contracts of the given `sc`.
174+
(define (update-side sc side)
175+
(match sc
176+
[(or/sc: scs ...)
177+
#:when (not (andmap flat-terminal-kind? scs))
178+
(weaken-side side)]
179+
[(? guarded-sc?)
180+
(strengthen-side side)]
181+
[_
182+
;; Keep same side by default.
183+
;; This is precisely safe for "unguarded" static contracts like and/sc
184+
;; and conservatively safe for everything else.
185+
side]))
186+
187+
;; guarded-sc? : sc? -> boolean?
188+
;; Returns #true if the given static contract represents a type with a "real"
189+
;; type constructor. E.g. list/sc is "real" and or/sc is not.
190+
(define (guarded-sc? sc)
191+
(match sc
192+
[(or (? flat-terminal-kind?)
193+
(->/sc: _ _ _ _ _ _)
194+
(arr/sc: _ _ _)
195+
(async-channel/sc: _)
196+
(box/sc: _)
197+
(channel/sc: _)
198+
(cons/sc: _ _)
199+
(continuation-mark-key/sc: _)
200+
(evt/sc: _)
201+
(hash/sc: _ _)
202+
(immutable-hash/sc: _ _)
203+
(list/sc: _ ...)
204+
(listof/sc: _)
205+
(mutable-hash/sc: _ _)
206+
(parameter/sc: _ _)
207+
(promise/sc: _)
208+
(prompt-tag/sc: _ _)
209+
(sequence/sc: _ ...)
210+
(set/sc: _)
211+
(struct/sc: _ _)
212+
(syntax/sc: _)
213+
(vector/sc: _ ...)
214+
(vectorof/sc: _)
215+
(weak-hash/sc: _ _))
216+
#true]
217+
[_
218+
#false]))
219+
137220
(define (remove-unused-recursive-contracts sc)
138221
(define root (generate-temporary))
139222
(define main-table (make-free-id-table))
@@ -208,12 +291,14 @@
208291
;; If we trust a specific side then we drop all contracts protecting that side.
209292
(define (optimize sc #:trusted-positive [trusted-positive #f] #:trusted-negative [trusted-negative #f])
210293
;; single-step: reduce and trusted-side-reduce if appropriate
211-
(define (single-step sc side)
294+
(define (single-step sc maybe-weak-side)
212295
(define trusted
213-
(case side
214-
[(positive) trusted-positive]
215-
[(negative) trusted-negative]
216-
[(both) (and trusted-positive trusted-negative)]))
296+
(if (weak-side? maybe-weak-side)
297+
#false
298+
(case maybe-weak-side
299+
[(positive) trusted-positive]
300+
[(negative) trusted-negative]
301+
[(both) (and trusted-positive trusted-negative)])))
217302

218303
(reduce
219304
(if trusted
@@ -223,8 +308,9 @@
223308
;; full-pass: single-step at every static contract subpart
224309
(define (full-pass sc)
225310
(define ((recur side) sc variance)
226-
(define new-side (combine-variance side variance))
227-
(single-step (sc-map sc (recur new-side)) new-side))
311+
(define curr-side (combine-variance side variance))
312+
(define next-side (update-side sc curr-side))
313+
(single-step (sc-map sc (recur next-side)) curr-side))
228314
((recur 'positive) sc 'covariant))
229315

230316
;; Do full passes until we reach a fix point, and then remove all unneccessary recursive parts
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#lang typed/racket/base
2+
3+
(module u racket/base
4+
(define (f b)
5+
(set-box! b "hello"))
6+
(provide f))
7+
8+
(define-type Maybe-Box (U #f (Boxof Integer)))
9+
10+
(require/typed 'u (f (-> Maybe-Box Void)))
11+
12+
(define b : Maybe-Box (box 4))
13+
14+
(module+ test
15+
(require typed/rackunit)
16+
17+
(check-exn exn:fail:contract?
18+
(λ () (f b)))
19+
20+
(check-equal?
21+
(if (box? b) (+ 1 (unbox b)) (error 'deadcode))
22+
5))

typed-racket-test/unit-tests/static-contract-optimizer-tests.rkt

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@
113113
#:neg (vectorof/sc none/sc))
114114

115115
;; Heterogeneous Vectors
116-
;; TODO fix ability to test equality here
117116
(check-optimize (vector/sc any/sc)
118117
#:pos any/sc
119118
#:neg (vector-length/sc 1))
@@ -179,6 +178,14 @@
179178
(check-optimize (or/sc none/sc none/sc)
180179
#:pos any/sc
181180
#:neg none/sc)
181+
(check-optimize (or/sc set?/sc (list/sc set?/sc) (list/sc set?/sc set?/sc))
182+
;; if all contracts are flat, optimize trusted positive
183+
#:pos any/sc
184+
#:neg (or/sc set?/sc (list/sc set?/sc) (list/sc set?/sc set?/sc)))
185+
(check-optimize (or/sc set?/sc (list/sc (flat/sc #'symbol?)) (box/sc (flat/sc #'symbol?)))
186+
;; don't optimize if any contracts are non-flat --- but do optimize under guarded constructors
187+
#:pos (or/sc set?/sc (list-length/sc 1) (box/sc (flat/sc #'symbol?)))
188+
#:neg (or/sc set?/sc (list/sc (flat/sc #'symbol?)) (box/sc (flat/sc #'symbol?))))
182189

183190
;; None
184191
(check-optimize none/sc
@@ -343,6 +350,33 @@
343350
(arr/sc empty #f (list set?/sc))
344351
(arr/sc (list any/sc) #f (list (listof/sc set?/sc))))))
345352

353+
;; more Or case
354+
(check-optimize
355+
;; (or (or ....)), both "or"s contain non-flat contracts --- don't optimize
356+
(or/sc cons?/sc (or/sc cons?/sc (box/sc cons?/sc)) (box/sc cons?/sc))
357+
#:pos (or/sc cons?/sc (or/sc cons?/sc (box/sc cons?/sc)) (box/sc cons?/sc))
358+
#:neg (or/sc cons?/sc (or/sc cons?/sc (box/sc cons?/sc)) (box/sc cons?/sc)))
359+
(check-optimize
360+
;; (or (or ...)), only the inner "or" contains a non-flat contract --- don't optimize
361+
(or/sc cons?/sc (or/sc cons?/sc (box/sc cons?/sc)))
362+
#:pos (or/sc cons?/sc (or/sc cons?/sc (box/sc cons?/sc)))
363+
#:neg (or/sc cons?/sc (or/sc cons?/sc (box/sc cons?/sc))))
364+
(check-optimize
365+
;; (or (or ...)), only the outer "or" contains a non-flat contract --- still don't optimize
366+
(or/sc (box/sc cons?/sc) (or/sc cons?/sc set?/sc))
367+
#:pos (or/sc (box/sc cons?/sc) (or/sc cons?/sc set?/sc))
368+
#:neg (or/sc (box/sc cons?/sc) (or/sc cons?/sc set?/sc)))
369+
(check-optimize
370+
;; (or (and/sc ...)) where the "or" has a non-flat "and" is all flat --- don't optimize
371+
;; this is just to make sure `and/sc` isn't treated specially
372+
(or/sc (box/sc cons?/sc) (and/sc cons?/sc list?/sc))
373+
#:pos (or/sc (box/sc cons?/sc) (and/sc cons?/sc list?/sc))
374+
#:neg (or/sc (box/sc cons?/sc) (and/sc cons?/sc list?/sc)))
375+
(check-optimize
376+
;; (or (and ...)) where both contain flat contracts --- could optimize, but would need to recognize the and/c is flat
377+
(or/sc set?/sc (and/sc cons?/sc list?/sc))
378+
#:pos (or/sc set?/sc (and/sc cons?/sc list?/sc))
379+
#:neg (or/sc set?/sc (and/sc cons?/sc list?/sc)))
346380
))
347381

348382
(define tests

0 commit comments

Comments
 (0)