Skip to content

Commit 5b01859

Browse files
committed
Get basic closure conversion somewhat working
Implement basic creation of closure types with all variables boxed. Variables outside the closure are not handled correctly yet. Desugaring of functions which become closures is tricky because we don't yet know that they're closures and we might move their method definitions to top level. To aid with this, introduce three intermediate forms: * `K"function_decl"` to create a local or global binding for the function name, * `K"function_type"` to evaluate the type (either global generic function or closure converted struct) * `K"method_defs"` to encapsulate code which creates methods and might be moved to top level. (Just using `K"method"` isn't ideal for this because we might want to evaluate some intermediate forms and reuse them for multiple method definitions for convenience and to avoid multiple evaluation of expressions with side effects. Eg when lowering functions with default positional args to multiple method defs.)
1 parent dcfd14e commit 5b01859

File tree

8 files changed

+494
-117
lines changed

8 files changed

+494
-117
lines changed

README.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,131 @@ The second step uses this metadata to
339339
* Lower const and non-const global assignments
340340
* TODO: probably more here.
341341

342+
343+
### Q&A
344+
345+
#### When does `function` introduce a closure?
346+
347+
Closures are just functions where the name of the function is *local* in scope.
348+
How does the function name become a local? The `function` keyword acts like an
349+
assignment to the function name for the purposes of scope resolution. Thus
350+
`function f() body end` is rather like `f = ()->body` and may result in the
351+
symbol `f` being either `local` or `global`. Like other assignments, `f` may be
352+
declared global or local explicitly, but if not `f` is subject to the usual
353+
rules for assignments inside scopes. For example, inside a `let` scope
354+
`function f() ...` would result in the symbol `f` being local.
355+
356+
Examples:
357+
358+
```julia
359+
begin
360+
# f is global because `begin ... end` does not introduce a scope
361+
function f()
362+
body
363+
end
364+
365+
# g is a closure because `g` is explicitly declared local
366+
local g
367+
function g()
368+
body
369+
end
370+
end
371+
372+
let
373+
# f is local so this is a closure becuase `let ... end` introduces a scope
374+
function f()
375+
body
376+
end
377+
378+
# g is not a closure because `g` is declared global
379+
global g
380+
function g()
381+
body
382+
end
383+
end
384+
```
385+
386+
#### How do captures work with non-closures?
387+
388+
Yes it's true, you can capture local variables into global methods. For example:
389+
390+
```julia
391+
begin
392+
local x = 1
393+
function f(y)
394+
x + y
395+
end
396+
x = 2
397+
end
398+
```
399+
400+
The way this works is to put `x` in a `Box` and interpolate it into the AST of
401+
`f` (the `Box` can be eliminated in some cases, but not here). Essentially this
402+
lowers to code which is almost-equivalent to the following:
403+
404+
```julia
405+
begin
406+
local x = Core.Box(1)
407+
@eval function f(y)
408+
$(x.contents) + y
409+
end
410+
x.contents = 2
411+
end
412+
```
413+
414+
#### How do captures work with closures with multiple methods?
415+
416+
Sometimes you might want a closure with multiple methods, but those methods
417+
might capture different local variables. For example,
418+
419+
```julia
420+
let
421+
x = 1
422+
y = 1.5
423+
function f(xx::Int)
424+
xx + x
425+
end
426+
function f(yy::Float64)
427+
yy + y
428+
end
429+
430+
f(42)
431+
end
432+
```
433+
434+
In this case, the closure type must capture both `x` and `y` and the generated
435+
code looks rather like this:
436+
437+
```julia
438+
struct TheClosureType
439+
x
440+
y
441+
end
442+
443+
let
444+
x = 1
445+
y = 1.5
446+
f = TheClosureType(x,y)
447+
function (self::TheClosureType)(xx::Int)
448+
xx + self.x
449+
end
450+
function (self::TheClosureType)(yy::Int)
451+
yy + self.y
452+
end
453+
454+
f(42)
455+
end
456+
```
457+
458+
#### When are `method` defs lifted to top level?
459+
460+
Closure method definitions must be lifted to top level whenever the definitions
461+
appear inside a function. This is allow efficient compilation and avoid world
462+
age issues.
463+
464+
Conversely, when method defs appear in top level code, they are executed
465+
inline.
466+
342467
## Pass 5: Convert to untyped IR
343468

344469
This pass is implemented in `linear_ir.jl`.

src/ast.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,12 @@ function new_mutable_var(ctx::AbstractLoweringContext, srcref, name; kind=:local
258258
var
259259
end
260260

261+
function new_global_binding(ctx::AbstractLoweringContext, srcref, name, mod; kws...)
262+
id = new_binding(ctx.bindings, BindingInfo(name, :global; is_internal=true, mod=mod, kws...))
263+
nameref = makeleaf(ctx, srcref, K"Identifier", name_val=name)
264+
makeleaf(ctx, nameref, K"BindingId", var_id=id)
265+
end
266+
261267
function alias_binding(ctx::AbstractLoweringContext, srcref)
262268
id = new_binding(ctx.bindings, BindingInfo("alias", :alias; is_internal=true))
263269
makeleaf(ctx, srcref, K"BindingId", var_id=id)

0 commit comments

Comments
 (0)