Skip to content

Commit 5e6524d

Browse files
authored
flambda-backend: Tail-calling local-returning functions should make the current function local-returning as well (#1498)
1 parent fa71f6b commit 5e6524d

File tree

4 files changed

+135
-45
lines changed

4 files changed

+135
-45
lines changed

testsuite/tests/typing-local/crossing.ml

+5-5
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ val f : unit -> local_ int = <fun>
224224
|}]
225225

226226
let g : _ -> _ =
227-
fun () -> f ()
227+
fun () -> let x = f () in x
228228
[%%expect{|
229229
val g : unit -> int = <fun>
230230
|}]
@@ -236,11 +236,11 @@ val f : unit -> local_ string = <fun>
236236
|}]
237237

238238
let g : _ -> _ =
239-
fun () -> f ()
239+
fun () -> let x = f () in x
240240
[%%expect{|
241-
Line 2, characters 12-16:
242-
2 | fun () -> f ()
243-
^^^^
241+
Line 2, characters 28-29:
242+
2 | fun () -> let x = f () in x
243+
^
244244
Error: This value escapes its region
245245
|}]
246246

testsuite/tests/typing-local/local.ml

+45-2
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,16 @@ Error: This value escapes its region
636636
Adding 1 more argument will make the value non-local
637637
|}]
638638

639+
(* The fixed version. Note that in the printed type, local returning is implicit
640+
*)
641+
let bug4_fixed : local_ (string -> foo:string -> unit) -> local_ (string -> unit) =
642+
fun f -> local_ f ~foo:"hello"
643+
[%%expect{|
644+
val bug4_fixed : local_ (string -> foo:string -> unit) -> string -> unit =
645+
<fun>
646+
|}]
647+
648+
639649
let bug4' () =
640650
let local_ f arg ~foo = () in
641651
let local_ perm ~foo = f ~foo in
@@ -763,7 +773,7 @@ val baduse : ('a -> 'b -> 'c) -> 'a -> 'b -> 'c lazy_t = <fun>
763773
Line 2, characters 20-45:
764774
2 | let result = baduse (fun a b -> local_ (a,b)) 1 2
765775
^^^^^^^^^^^^^^^^^^^^^^^^^
766-
Error: This function is local returning, but was expected otherwise
776+
Error: This function is local-returning, but was expected otherwise
767777
|}]
768778

769779

@@ -1383,6 +1393,39 @@ let foo () =
13831393
val foo : unit -> int = <fun>
13841394
|}]
13851395

1396+
(* tail-calling local-returning functions make the current function
1397+
local-returning as well; mode-crossing is irrelavent here. Whether or not the
1398+
function actually allocates in parent-region is also irrelavent here, but we
1399+
allocate just to demonstrate the potential leaking. *)
1400+
let foo () = local_
1401+
let _ = local_ (52, 24) in
1402+
42
1403+
[%%expect{|
1404+
val foo : unit -> local_ int = <fun>
1405+
|}]
1406+
1407+
let bar () =
1408+
let _x = 52 in
1409+
foo ()
1410+
[%%expect{|
1411+
val bar : unit -> local_ int = <fun>
1412+
|}]
1413+
1414+
(* if not at tail, then not affected *)
1415+
let bar' () =
1416+
let _x = foo () in
1417+
52
1418+
[%%expect{|
1419+
val bar' : unit -> int = <fun>
1420+
|}]
1421+
1422+
(* nontail attribute works as well *)
1423+
let bar' () =
1424+
foo () [@nontail]
1425+
[%%expect{|
1426+
val bar' : unit -> int = <fun>
1427+
|}]
1428+
13861429
(* Parameter modes must be matched by the type *)
13871430

13881431
let foo : 'a -> unit = fun (local_ x) -> ()
@@ -1406,7 +1449,7 @@ let foo : unit -> string = fun () -> local_ "hello"
14061449
Line 1, characters 27-51:
14071450
1 | let foo : unit -> string = fun () -> local_ "hello"
14081451
^^^^^^^^^^^^^^^^^^^^^^^^
1409-
Error: This function is local returning, but was expected otherwise
1452+
Error: This function is local-returning, but was expected otherwise
14101453
|}]
14111454
14121455
(* Unboxed type constructors do not affect regionality *)

typing/typecore.ml

+84-38
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ type error =
185185
| Uncurried_function_escapes
186186
| Local_return_annotation_mismatch of Location.t
187187
| Function_returns_local
188+
| Tail_call_local_returning
188189
| Bad_tail_annotation of [`Conflict|`Not_a_tailcall]
189190
| Optional_poly_param
190191
| Exclave_in_nontail_position
@@ -268,20 +269,20 @@ let mk_expected ?explanation ty = { ty; explanation; }
268269
let case lhs rhs =
269270
{c_lhs = lhs; c_guard = None; c_rhs = rhs}
270271

271-
type function_position = Tail | Nontail
272+
type position_in_function = FTail | FNontail
272273

273274

274-
type region_position =
275+
type position_in_region =
275276
(* not the tail of a region*)
276277
| RNontail
277278
(* tail of a region,
278279
together with the mode of that region,
279280
and whether it is also the tail of a function
280281
(for tail call escape detection) *)
281-
| RTail of Value_mode.t * function_position
282+
| RTail of Value_mode.t * position_in_function
282283

283284
type expected_mode =
284-
{ position : region_position;
285+
{ position : position_in_region;
285286
escaping_context : Env.escaping_context option;
286287
(* the upper bound of mode*)
287288
mode : Value_mode.t;
@@ -306,23 +307,59 @@ type expected_mode =
306307
(* for t in tuple_modes, t <= regional_to_global mode *)
307308
}
308309

309-
let tail_call_escape = function
310-
| RTail (_, Tail) -> true
311-
| _ -> false
310+
type position_and_mode = {
311+
(* apply_position of the current application *)
312+
apply_position : apply_position;
313+
(* [Some m] if [position] is [Tail], where m is the mode of the surrounding
314+
function's return mode *)
315+
region_mode : Value_mode.t option;
316+
}
317+
318+
let position_and_mode_default = {
319+
apply_position = Default;
320+
region_mode = None;
321+
}
312322

313-
let apply_position env (expected_mode : expected_mode) sexp : apply_position =
323+
(** The function produces two values, apply_position and region_mode.
324+
Invariant: if apply_position = Tail, then region_mode = Some ... *)
325+
let position_and_mode env (expected_mode : expected_mode) sexp
326+
: position_and_mode =
314327
let fail err =
315328
raise (Error (sexp.pexp_loc, env, Bad_tail_annotation err))
316329
in
317-
match
318-
Builtin_attributes.tailcall sexp.pexp_attributes,
319-
tail_call_escape expected_mode.position
320-
with
321-
| Ok (None | Some `Tail_if_possible), false -> Default
322-
| Ok (None | Some `Tail | Some `Tail_if_possible), true -> Tail
323-
| Ok (Some `Nontail), _ -> Nontail
324-
| Ok (Some `Tail), false -> fail `Not_a_tailcall
325-
| Error `Conflict, _ -> fail `Conflict
330+
let requested =
331+
match Builtin_attributes.tailcall sexp.pexp_attributes with
332+
| Ok r -> r
333+
| Error `Conflict -> fail `Conflict
334+
in
335+
match expected_mode.position with
336+
| RTail (m ,FTail) -> begin
337+
match requested with
338+
| Some `Tail | Some `Tail_if_possible | None ->
339+
{apply_position = Tail; region_mode = Some m}
340+
| Some `Nontail -> {apply_position = Nontail; region_mode = None}
341+
end
342+
| RNontail | RTail(_, FNontail) -> begin
343+
match requested with
344+
| None | Some `Tail_if_possible ->
345+
{apply_position = Default; region_mode = None}
346+
| Some `Nontail -> {apply_position = Nontail; region_mode = None}
347+
| Some `Tail -> fail `Not_a_tailcall
348+
end
349+
350+
(* ap_mode is the return mode of the current application *)
351+
let check_tail_call_local_returning loc env ap_mode {region_mode; _} =
352+
match region_mode with
353+
| Some region_mode -> begin
354+
(* This application is at the tail of a function with a region;
355+
if ap_mode is local, funct_ret_mode needs to be local as well. *)
356+
match
357+
Value_mode.submode (Value_mode.of_alloc ap_mode) region_mode
358+
with
359+
| Ok () -> ()
360+
| Error _ -> raise (Error (loc, env, Tail_call_local_returning))
361+
end
362+
| None -> ()
326363

327364
let mode_default mode =
328365
{ position = RNontail;
@@ -335,14 +372,14 @@ let mode_default mode =
335372
mode is the mode of the function region *)
336373
let mode_return mode =
337374
{ (mode_default (Value_mode.local_to_regional mode)) with
338-
position = RTail (mode, Tail);
375+
position = RTail (mode, FTail);
339376
escaping_context = Some Return;
340377
}
341378

342379
(* used when entering a region.*)
343380
let mode_region mode =
344381
{ (mode_default (Value_mode.local_to_regional mode)) with
345-
position = RTail (mode, Nontail);
382+
position = RTail (mode, FNontail);
346383
escaping_context = None;
347384
}
348385

@@ -405,7 +442,7 @@ let mode_argument ~funct ~index ~position ~partial_app alloc_mode =
405442

406443
let mode_lazy =
407444
{ mode_global with
408-
position = RTail (Value_mode.global, Tail) }
445+
position = RTail (Value_mode.global, FTail) }
409446

410447

411448
let submode ~loc ~env ~reason mode expected_mode =
@@ -4412,9 +4449,9 @@ and type_expect_
44124449
end
44134450
| Pexp_apply(sfunct, sargs) ->
44144451
assert (sargs <> []);
4415-
let position = apply_position env expected_mode sexp in
4452+
let pm = position_and_mode env expected_mode sexp in
44164453
let funct_mode, funct_expected_mode =
4417-
match position with
4454+
match pm.apply_position with
44184455
| Tail ->
44194456
let mode = Value_mode.local_to_regional (Value_mode.newvar ()) in
44204457
mode, mode_tailcall_function mode
@@ -4479,12 +4516,12 @@ and type_expect_
44794516
| _ ->
44804517
(rt, funct), sargs
44814518
in
4482-
let (args, ty_res, ap_mode, position) =
4483-
type_application env loc expected_mode position funct funct_mode sargs rt
4519+
let (args, ty_res, ap_mode, pm) =
4520+
type_application env loc expected_mode pm funct funct_mode sargs rt
44844521
in
44854522

44864523
rue {
4487-
exp_desc = Texp_apply(funct, args, position, ap_mode);
4524+
exp_desc = Texp_apply(funct, args, pm.apply_position, ap_mode);
44884525
exp_loc = loc; exp_extra = [];
44894526
exp_type = ty_res;
44904527
exp_attributes = sexp.pexp_attributes;
@@ -4903,7 +4940,7 @@ and type_expect_
49034940
(mk_expected ~explanation:While_loop_conditional Predef.type_bool)
49044941
in
49054942
let body_env = Env.add_region_lock env in
4906-
let position = RTail (Value_mode.local, Nontail) in
4943+
let position = RTail (Value_mode.local, FNontail) in
49074944
let wh_body =
49084945
type_statement ~explanation:While_loop_body
49094946
~position body_env sbody
@@ -4929,7 +4966,7 @@ and type_expect_
49294966
type_for_loop_index ~loc ~env ~param
49304967
in
49314968
let new_env = Env.add_region_lock new_env in
4932-
let position = RTail (Value_mode.local, Nontail) in
4969+
let position = RTail (Value_mode.local, FNontail) in
49334970
let for_body =
49344971
type_statement ~explanation:For_loop_body ~position new_env sbody
49354972
in
@@ -5050,7 +5087,7 @@ and type_expect_
50505087
| Pexp_send (e, {txt=met}) ->
50515088
if !Clflags.principal then begin_def ();
50525089
let obj = type_exp env mode_global e in
5053-
let ap_pos = apply_position env expected_mode sexp in
5090+
let pm = position_and_mode env expected_mode sexp in
50545091
let (meth, typ) =
50555092
match obj.exp_desc with
50565093
| Texp_ident(_, _, {val_kind = Val_self(sign, meths, _, _)}, _) ->
@@ -5154,7 +5191,7 @@ and type_expect_
51545191
assert false
51555192
in
51565193
rue {
5157-
exp_desc = Texp_send(obj, meth, ap_pos,
5194+
exp_desc = Texp_send(obj, meth, pm.apply_position,
51585195
register_allocation expected_mode
51595196
);
51605197
exp_loc = loc; exp_extra = [];
@@ -5163,14 +5200,14 @@ and type_expect_
51635200
exp_env = env }
51645201
| Pexp_new cl ->
51655202
let (cl_path, cl_decl) = Env.lookup_class ~loc:cl.loc cl.txt env in
5166-
let ap_pos = apply_position env expected_mode sexp in
5203+
let pm = position_and_mode env expected_mode sexp in
51675204
begin match cl_decl.cty_new with
51685205
None ->
51695206
raise(Error(loc, env, Virtual_class cl.txt))
51705207
| Some ty ->
51715208
rue {
51725209
exp_desc =
5173-
Texp_new (cl_path, cl, cl_decl, ap_pos);
5210+
Texp_new (cl_path, cl, cl_decl, pm.apply_position);
51745211
exp_loc = loc; exp_extra = [];
51755212
exp_type = instance ty;
51765213
exp_attributes = sexp.pexp_attributes;
@@ -6465,7 +6502,8 @@ and type_apply_arg env ~app_loc ~funct ~index ~position ~partial_app (lbl, arg)
64656502
(lbl, Arg (arg, Value_mode.global))
64666503
| Omitted _ as arg -> (lbl, arg)
64676504

6468-
and type_application env app_loc expected_mode position funct funct_mode sargs ret_tvar =
6505+
and type_application env app_loc expected_mode pm
6506+
funct funct_mode sargs ret_tvar =
64696507
let is_ignore funct =
64706508
is_prim ~name:"%ignore" funct &&
64716509
(try ignore (filter_arrow_mono env (instance funct.exp_type) Nolabel); true
@@ -6489,11 +6527,12 @@ and type_application env app_loc expected_mode position funct funct_mode sargs r
64896527
submode ~loc:app_loc ~env ~reason:Other
64906528
mode_res expected_mode;
64916529
let marg =
6492-
mode_argument ~funct ~index:0 ~position ~partial_app:false marg
6530+
mode_argument ~funct ~index:0 ~position:(pm.apply_position)
6531+
~partial_app:false marg
64936532
in
64946533
let exp = type_expect env marg sarg (mk_expected ty_arg) in
64956534
check_partial_application ~statement:false exp;
6496-
([Nolabel, Arg exp], ty_res, ap_mode, position)
6535+
([Nolabel, Arg exp], ty_res, ap_mode, pm)
64976536
| _ ->
64986537
let ty = funct.exp_type in
64996538
let ignore_labels =
@@ -6519,10 +6558,11 @@ and type_application env app_loc expected_mode position funct funct_mode sargs r
65196558
(Value_mode.regional_to_local_alloc funct_mode) sargs ret_tvar
65206559
in
65216560
let partial_app = is_partial_apply untyped_args in
6522-
let position = if partial_app then Default else position in
6561+
let pm = if partial_app then position_and_mode_default else pm in
65236562
let args =
65246563
List.mapi (fun index arg ->
6525-
type_apply_arg env ~app_loc ~funct ~index ~position ~partial_app arg)
6564+
type_apply_arg env ~app_loc ~funct ~index
6565+
~position:(pm.apply_position) ~partial_app arg)
65266566
untyped_args
65276567
in
65286568
let ty_ret, mode_ret, args =
@@ -6540,7 +6580,9 @@ and type_application env app_loc expected_mode position funct funct_mode sargs r
65406580
in
65416581
submode ~loc:app_loc ~env ~reason:(Application ty_ret)
65426582
mode_ret expected_mode;
6543-
args, ty_ret, ap_mode, position
6583+
6584+
check_tail_call_local_returning app_loc env ap_mode pm;
6585+
args, ty_ret, ap_mode, pm
65446586

65456587
and type_construct env (expected_mode : expected_mode) loc lid sarg
65466588
ty_expected_explained attrs =
@@ -8312,7 +8354,11 @@ let report_error ~loc env = function
83128354
"Optional parameters cannot be polymorphic"
83138355
| Function_returns_local ->
83148356
Location.errorf ~loc
8315-
"This function is local returning, but was expected otherwise"
8357+
"This function is local-returning, but was expected otherwise"
8358+
| Tail_call_local_returning ->
8359+
Location.errorf ~loc
8360+
"@[This application is local-returning, but is at the tail @ \
8361+
position of a function that is not local-returning@]"
83168362
| Layout_not_enabled c ->
83178363
Location.errorf ~loc
83188364
"@[Layout %s is used here, but the appropriate layouts extension is \

typing/typecore.mli

+1
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ type error =
262262
| Uncurried_function_escapes
263263
| Local_return_annotation_mismatch of Location.t
264264
| Function_returns_local
265+
| Tail_call_local_returning
265266
| Bad_tail_annotation of [`Conflict|`Not_a_tailcall]
266267
| Optional_poly_param
267268
| Exclave_in_nontail_position

0 commit comments

Comments
 (0)