Skip to content

Commit 3574fda

Browse files
JeffBezansonKeno
authored andcommitted
add front-end support for OpaqueClosure
1 parent 9cc49d3 commit 3574fda

File tree

6 files changed

+115
-47
lines changed

6 files changed

+115
-47
lines changed

base/opaque_closure.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ end
2424

2525

2626
# @opaque macro goes here
27+
macro opaque(ex)
28+
Expr(:opaque_closure, esc(ex))
29+
end

src/ast.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,13 @@ static jl_value_t *scm_to_julia_(fl_context_t *fl_ctx, value_t e, jl_module_t *m
604604
temp = (jl_value_t*)jl_exprn(sym, 1);
605605
jl_exprargset(temp, 0, ex);
606606
}
607+
else if (sym == jl_symbol("anonymous_closure")) {
608+
ex = scm_to_julia_(fl_ctx, car_(e), mod);
609+
assert(jl_is_code_info(ex));
610+
jl_linenumber_to_lineinfo((jl_code_info_t*)ex, mod, (jl_value_t*)jl_symbol("opaque"));
611+
jl_resolve_globals_in_ir((jl_array_t*)((jl_code_info_t*)ex)->code, mod, NULL, 0);
612+
temp = ex;
613+
}
607614
if (temp) {
608615
JL_GC_POP();
609616
return temp;

src/julia-syntax.scm

Lines changed: 81 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,6 +1965,24 @@
19651965
,@(if (length= e 3) '(()) '())
19661966
,@(map expand-forms (cddr e))))
19671967

1968+
'opaque_closure
1969+
(lambda (e)
1970+
(let* ((meth (caddr (caddr (expand-forms (cadr e))))) ;; `method` expr
1971+
(lam (cadddr meth))
1972+
(sig-block (caddr meth))
1973+
(sig-block (if (and (pair? sig-block) (eq? (car sig-block) 'block))
1974+
sig-block
1975+
`(block ,sig-block)))
1976+
(stmts (cdr (butlast sig-block)))
1977+
(sig-svec (last sig-block))
1978+
(typ-svec (caddr sig-svec))
1979+
(tvars (cddr (cadddr sig-svec)))
1980+
(argtypes (cdddr typ-svec))
1981+
(argtype (foldl (lambda (var ex) `(call (core UnionAll) ,var ,ex))
1982+
(expand-forms `(curly (core Tuple) ,@argtypes))
1983+
(reverse tvars))))
1984+
`(opaque_closure ,argtype ,lam)))
1985+
19681986
'block
19691987
(lambda (e)
19701988
(cond ((null? (cdr e)) '(null))
@@ -3049,9 +3067,9 @@ f(x) = yt(x)
30493067
(define (clear-capture-bits vinfos)
30503068
(map vinfo:not-capt vinfos))
30513069

3052-
(define (convert-lambda lam fname interp capt-sp)
3070+
(define (convert-lambda lam fname interp capt-sp opaq)
30533071
(let ((body (add-box-inits-to-body
3054-
lam (cl-convert (cadddr lam) fname lam (table) (table) #f interp))))
3072+
lam (cl-convert (cadddr lam) fname lam (table) (table) #f interp opaq))))
30553073
`(lambda ,(lam:args lam)
30563074
(,(clear-capture-bits (car (lam:vinfo lam)))
30573075
()
@@ -3091,12 +3109,17 @@ f(x) = yt(x)
30913109
`(block (= ,temp ,(renumber-assigned-ssavalues t)) ,ex)
30923110
ex))))
30933111

3112+
(define (capt-var-access var fname opaq)
3113+
(if opaq
3114+
`(call (core getfield) ,fname ,(get opaq var))
3115+
`(call (core getfield) ,fname (inert ,var))))
3116+
30943117
;; convert assignment to a closed variable to a setfield! call.
30953118
;; while we're at it, generate `convert` calls for variables with
30963119
;; declared types.
30973120
;; when doing this, the original value needs to be preserved, to
30983121
;; ensure the expression `a=b` always returns exactly `b`.
3099-
(define (convert-assignment var rhs0 fname lam interp)
3122+
(define (convert-assignment var rhs0 fname lam interp opaq)
31003123
(cond
31013124
((symbol? var)
31023125
(let* ((vi (assq var (car (lam:vinfo lam))))
@@ -3114,11 +3137,11 @@ f(x) = yt(x)
31143137
(make-ssavalue)))
31153138
(rhs (if (equal? vt '(core Any))
31163139
rhs1
3117-
(convert-for-type-decl rhs1 (cl-convert vt fname lam #f #f #f interp))))
3140+
(convert-for-type-decl rhs1 (cl-convert vt fname lam #f #f #f interp opaq))))
31183141
(ex (cond (closed `(call (core setfield!)
31193142
,(if interp
31203143
`($ ,var)
3121-
`(call (core getfield) ,fname (inert ,var)))
3144+
(capt-var-access var fname opaq))
31223145
(inert contents)
31233146
,rhs))
31243147
(capt `(call (core setfield!) ,var (inert contents) ,rhs))
@@ -3391,23 +3414,30 @@ f(x) = yt(x)
33913414
(define (toplevel-preserving? e)
33923415
(and (pair? e) (memq (car e) '(if elseif block trycatch tryfinally))))
33933416

3394-
(define (map-cl-convert exprs fname lam namemap defined toplevel interp)
3417+
(define (map-cl-convert exprs fname lam namemap defined toplevel interp opaq)
33953418
(if toplevel
33963419
(map (lambda (x)
33973420
(let ((tl (lift-toplevel (cl-convert x fname lam namemap defined
33983421
(and toplevel (toplevel-preserving? x))
3399-
interp))))
3422+
interp opaq))))
34003423
(if (null? (cdr tl))
34013424
(car tl)
34023425
`(block ,@(cdr tl) ,(car tl)))))
34033426
exprs)
3404-
(map (lambda (x) (cl-convert x fname lam namemap defined #f interp)) exprs)))
3427+
(map (lambda (x) (cl-convert x fname lam namemap defined #f interp opaq)) exprs)))
3428+
3429+
(define (prepare-lam lam)
3430+
;; mark all non-arguments as assigned, since locals that are never assigned
3431+
;; need to be handled the same as those that are (i.e., boxed).
3432+
(for-each (lambda (vi) (vinfo:set-asgn! vi #t))
3433+
(list-tail (car (lam:vinfo lam)) (length (lam:args lam))))
3434+
(lambda-optimize-vars! lam))
34053435

3406-
(define (cl-convert e fname lam namemap defined toplevel interp)
3436+
(define (cl-convert e fname lam namemap defined toplevel interp opaq)
34073437
(if (and (not lam)
3408-
(not (and (pair? e) (memq (car e) '(lambda method macro)))))
3438+
(not (and (pair? e) (memq (car e) '(lambda method macro opaque_closure)))))
34093439
(if (atom? e) e
3410-
(cons (car e) (map-cl-convert (cdr e) fname lam namemap defined toplevel interp)))
3440+
(cons (car e) (map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq)))
34113441
(cond
34123442
((symbol? e)
34133443
(define (new-undef-var name)
@@ -3426,7 +3456,7 @@ f(x) = yt(x)
34263456
(val (if (equal? typ '(core Any))
34273457
val
34283458
`(call (core typeassert) ,val
3429-
,(cl-convert typ fname lam namemap defined toplevel interp)))))
3459+
,(cl-convert typ fname lam namemap defined toplevel interp opaq)))))
34303460
`(block
34313461
,@(if (eq? box access) '() `((= ,access ,box)))
34323462
,undefcheck
@@ -3438,7 +3468,7 @@ f(x) = yt(x)
34383468
(cv
34393469
(let ((access (if interp
34403470
`($ (call (core QuoteNode) ,e))
3441-
`(call (core getfield) ,fname (inert ,e)))))
3471+
(capt-var-access e fname opaq))))
34423472
(if (and (vinfo:asgn cv) (vinfo:capt cv))
34433473
(get-box-contents access (vinfo:type cv))
34443474
access)))
@@ -3458,8 +3488,8 @@ f(x) = yt(x)
34583488
e)
34593489
((=)
34603490
(let ((var (cadr e))
3461-
(rhs (cl-convert (caddr e) fname lam namemap defined toplevel interp)))
3462-
(convert-assignment var rhs fname lam interp)))
3491+
(rhs (cl-convert (caddr e) fname lam namemap defined toplevel interp opaq)))
3492+
(convert-assignment var rhs fname lam interp opaq)))
34633493
((local-def) ;; make new Box for local declaration of defined variable
34643494
(let ((vi (assq (cadr e) (car (lam:vinfo lam)))))
34653495
(if (and vi (vinfo:asgn vi) (vinfo:capt vi))
@@ -3487,14 +3517,28 @@ f(x) = yt(x)
34873517
(if (and (vinfo:asgn cv) (vinfo:capt cv))
34883518
(let ((access (if interp
34893519
`($ (call (core QuoteNode) ,sym))
3490-
`(call (core getfield) ,fname (inert ,sym)))))
3520+
(capt-var-access sym fname opaq))))
34913521
`(call (core isdefined) ,access (inert contents)))
34923522
'(true)))
34933523
(vi
34943524
(if (and (vinfo:asgn vi) (vinfo:capt vi))
34953525
`(call (core isdefined) ,sym (inert contents))
34963526
e))
34973527
(else e))))
3528+
((opaque_closure)
3529+
(let* ((lam2 (caddr e))
3530+
(vis (lam:vinfo lam2))
3531+
(cvs (map car (cadr vis))))
3532+
(prepare-lam lam2)
3533+
(let ((var-exprs (map (lambda (v)
3534+
(let ((cv (assq v (cadr (lam:vinfo lam)))))
3535+
(if cv
3536+
(capt-var-access v fname opaq)
3537+
v)))
3538+
cvs)))
3539+
`(call (core _opaque_closure) ,(cadr e) (call (core apply_type) Union) (core Any)
3540+
(anonymous_closure ,(convert-lambda lam2 (car (lam:args lam2)) #f '() (symbol-to-idx-map cvs)))
3541+
,@var-exprs))))
34983542
((method)
34993543
(let* ((name (method-expr-name e))
35003544
(short (length= e 2)) ;; function f end
@@ -3507,7 +3551,7 @@ f(x) = yt(x)
35073551
(sp-inits (if (or short (not (eq? (car sig) 'block)))
35083552
'()
35093553
(map-cl-convert (butlast (cdr sig))
3510-
fname lam namemap defined toplevel interp)))
3554+
fname lam namemap defined toplevel interp opaq)))
35113555
(sig (and sig (if (eq? (car sig) 'block)
35123556
(last sig)
35133557
sig))))
@@ -3516,13 +3560,7 @@ f(x) = yt(x)
35163560
(error (string "cannot add method to function argument " name)))
35173561
(if (eqv? (string.char (string name) 0) #\@)
35183562
(error "macro definition not allowed inside a local scope"))))
3519-
(if lam2
3520-
(begin
3521-
;; mark all non-arguments as assigned, since locals that are never assigned
3522-
;; need to be handled the same as those that are (i.e., boxed).
3523-
(for-each (lambda (vi) (vinfo:set-asgn! vi #t))
3524-
(list-tail (car (lam:vinfo lam2)) (length (lam:args lam2))))
3525-
(lambda-optimize-vars! lam2)))
3563+
(if lam2 (prepare-lam lam2))
35263564
(if (not local) ;; not a local function; will not be closure converted to a new type
35273565
(cond (short (if (has? defined (cadr e))
35283566
e
@@ -3540,21 +3578,21 @@ f(x) = yt(x)
35403578
;; anonymous functions with keyword args generate global
35413579
;; functions that refer to the type of a local function
35423580
(rename-sig-types sig namemap)
3543-
fname lam namemap defined toplevel interp)
3581+
fname lam namemap defined toplevel interp opaq)
35443582
,(let ((body (add-box-inits-to-body
35453583
lam2
3546-
(cl-convert (cadddr lam2) 'anon lam2 (table) (table) #f interp))))
3584+
(cl-convert (cadddr lam2) 'anon lam2 (table) (table) #f interp opaq))))
35473585
`(lambda ,(cadr lam2)
35483586
(,(clear-capture-bits (car vis))
35493587
,@(cdr vis))
35503588
,body)))))
35513589
(else
3552-
(let* ((exprs (lift-toplevel (convert-lambda lam2 '|#anon| #t '())))
3590+
(let* ((exprs (lift-toplevel (convert-lambda lam2 '|#anon| #t '() #f)))
35533591
(top-stmts (cdr exprs))
35543592
(newlam (compact-and-renumber (linearize (car exprs)) 'none 0)))
35553593
`(toplevel-butfirst
35563594
(block ,@sp-inits
3557-
(method ,name ,(cl-convert sig fname lam namemap defined toplevel interp)
3595+
(method ,name ,(cl-convert sig fname lam namemap defined toplevel interp opaq)
35583596
,(julia-bq-macro newlam)))
35593597
,@top-stmts))))
35603598

@@ -3657,19 +3695,19 @@ f(x) = yt(x)
36573695
(append (map (lambda (gs tvar)
36583696
(make-assignment gs `(call (core TypeVar) ',tvar (core Any))))
36593697
closure-param-syms closure-param-names)
3660-
`((method #f ,(cl-convert arg-defs fname lam namemap defined toplevel interp)
3698+
`((method #f ,(cl-convert arg-defs fname lam namemap defined toplevel interp opaq)
36613699
,(convert-lambda lam2
36623700
(if iskw
36633701
(caddr (lam:args lam2))
36643702
(car (lam:args lam2)))
3665-
#f closure-param-names)))))))
3703+
#f closure-param-names #f)))))))
36663704
(mk-closure ;; expression to make the closure
36673705
(let* ((var-exprs (map (lambda (v)
36683706
(let ((cv (assq v (cadr (lam:vinfo lam)))))
36693707
(if cv
36703708
(if interp
36713709
`($ (call (core QuoteNode) ,v))
3672-
`(call (core getfield) ,fname (inert ,v)))
3710+
(capt-var-access v fname opaq))
36733711
v)))
36743712
capt-vars))
36753713
(P (append
@@ -3696,7 +3734,7 @@ f(x) = yt(x)
36963734
(begin
36973735
(put! defined name #t)
36983736
`(toplevel-butfirst
3699-
,(convert-assignment name mk-closure fname lam interp)
3737+
,(convert-assignment name mk-closure fname lam interp opaq)
37003738
,@typedef
37013739
,@(map (lambda (v) `(moved-local ,v)) moved-vars)
37023740
,@sp-inits
@@ -3709,14 +3747,14 @@ f(x) = yt(x)
37093747
(table)
37103748
(table)
37113749
(null? (cadr e)) ;; only toplevel thunks have 0 args
3712-
interp)))
3750+
interp opaq)))
37133751
`(lambda ,(cadr e)
37143752
(,(clear-capture-bits (car (lam:vinfo e)))
37153753
() ,@(cddr (lam:vinfo e)))
37163754
(block ,@body))))
37173755
;; remaining `::` expressions are type assertions
37183756
((|::|)
3719-
(cl-convert `(call (core typeassert) ,@(cdr e)) fname lam namemap defined toplevel interp))
3757+
(cl-convert `(call (core typeassert) ,@(cdr e)) fname lam namemap defined toplevel interp opaq))
37203758
;; remaining `decl` expressions are only type assertions if the
37213759
;; argument is global or a non-symbol.
37223760
((decl)
@@ -3726,15 +3764,15 @@ f(x) = yt(x)
37263764
(else
37273765
(if (or (symbol? (cadr e)) (and (pair? (cadr e)) (eq? (caadr e) 'outerref)))
37283766
(error "type declarations on global variables are not yet supported"))
3729-
(cl-convert `(call (core typeassert) ,@(cdr e)) fname lam namemap defined toplevel interp))))
3767+
(cl-convert `(call (core typeassert) ,@(cdr e)) fname lam namemap defined toplevel interp opaq))))
37303768
;; `with-static-parameters` expressions can be removed now; used only by analyze-vars
37313769
((with-static-parameters)
3732-
(cl-convert (cadr e) fname lam namemap defined toplevel interp))
3770+
(cl-convert (cadr e) fname lam namemap defined toplevel interp opaq))
37333771
(else
37343772
(cons (car e)
3735-
(map-cl-convert (cdr e) fname lam namemap defined toplevel interp))))))))
3773+
(map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq))))))))
37363774

3737-
(define (closure-convert e) (cl-convert e #f #f #f #f #f #f))
3775+
(define (closure-convert e) (cl-convert e #f #f #f #f #f #f #f))
37383776

37393777
;; pass 5: convert to linear IR
37403778

@@ -4250,6 +4288,11 @@ f(x) = yt(x)
42504288
(cond (tail (emit-return temp))
42514289
(value temp)
42524290
(else (emit temp)))))
4291+
((anonymous_closure)
4292+
(let ((temp `(anonymous_closure ,(linearize (cadr e)))))
4293+
(cond (tail (emit-return temp))
4294+
(value temp)
4295+
(else (emit temp)))))
42534296

42544297
;; top level expressions
42554298
((thunk module)
@@ -4473,14 +4516,6 @@ f(x) = yt(x)
44734516
(loop (cdr stmts)))))
44744517
(vector (reverse code) (reverse locs) (reverse linetable) ssavtable labltable)))
44754518

4476-
(define (symbol-to-idx-map lst)
4477-
(let ((tbl (table)))
4478-
(let loop ((xs lst) (i 1))
4479-
(if (pair? xs)
4480-
(begin (put! tbl (car xs) i)
4481-
(loop (cdr xs) (+ i 1)))))
4482-
tbl))
4483-
44844519
(define (renumber-lambda lam file line)
44854520
(let* ((stuff (compact-ir (lam:body lam) file line))
44864521
(code (aref stuff 0))

src/opaque_closure.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ JL_DLLEXPORT jl_value_t *jl_invoke_opaque_closure(jl_opaque_closure_t *clos, jl_
1212
jl_value_t *ret;
1313
JL_GC_PUSH1(&ret);
1414
if (jl_is_method(clos->source)) {
15-
ret = jl_gf_invoke_by_method((jl_method_t*)clos->source, (jl_value_t*)clos, args, nargs + 1);
15+
// args[0] is implicitly the environment, not the closure object itself.
16+
// N.B.: jl_interpret_opaque_closure handles this internally.
17+
ret = jl_gf_invoke_by_method((jl_method_t*)clos->source, (jl_value_t*)clos->env, args, nargs + 1);
1618
} else {
1719
ret = jl_interpret_opaque_closure(clos, args, nargs);
1820
}

src/utils.scm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,12 @@
9393
any
9494
(loop (cdr lst)
9595
(or (pred (car lst)) any)))))
96+
97+
;; construct a table mapping each element of `lst` to its index (1-indexed)
98+
(define (symbol-to-idx-map lst)
99+
(let ((tbl (table)))
100+
(let loop ((xs lst) (i 1))
101+
(if (pair? xs)
102+
(begin (put! tbl (car xs) i)
103+
(loop (cdr xs) (+ i 1)))))
104+
tbl))

test/opaque_closure.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,15 @@ let A = [1 2]
9494
@test Oc() == 2
9595
end
9696
end
97+
98+
using Base: @opaque
99+
100+
@test @opaque(x->2x)(8) == 16
101+
let f = @opaque (x::Int, y::Float64)->(2x, 3y)
102+
@test_throws TypeError f(1, 1)
103+
@test f(2, 3.0) === (4, 9.0)
104+
end
105+
function uses_frontend_opaque(x)
106+
@opaque y->x+y
107+
end
108+
@test uses_frontend_opaque(10)(8) == 18

0 commit comments

Comments
 (0)