Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/bound-inline-depth.polish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Stop inlining nested calls more than four levels deep, so a chain of small functions that call each other no longer makes compile time and output size grow quadratically. https://github.com/rescript-lang/rescript/pull/8801
25 changes: 21 additions & 4 deletions compiler/core/lam_pass_remove_alias.ml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,15 @@ let is_const_some (cst : Lambda.structured_constant) : bool =
| Const_some _ -> true
| _ -> false

(* How deep inlining may nest: simplifying an inlined body can inline the
functions it calls, and so on. Without a bound, a chain of small functions
that each call the previous one gets the whole chain inlined into every
function, which is quadratic in the length of the chain. Functions marked
[@inline] are always inlined. *)
let max_inline_depth = 4

let simplify_alias (meta : Lam_stats.t) (lam : Lambda.t) : Lambda.t =
let inline_depth = ref 0 in
let rec simpl (lam : Lambda.t) : Lambda.t =
match lam with
(* Keep a JSX tag like [M.make] as it is: flattening it may give a variable
Expand Down Expand Up @@ -204,10 +212,19 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lambda.t) : Lambda.t =
Lam_hit.hit_variable v lam)
(*avoid nontermination, e.g, `g(g)`*)
then normal ()
else
simpl
(Lam_beta_reduce.propagate_beta_reduce_with_map meta
param_map params body ap_args))
else if
!inline_depth >= max_inline_depth
&& m.attr.inline <> Always_inline
then normal ()
else (
incr inline_depth;
let inlined =
simpl
(Lam_beta_reduce.propagate_beta_reduce_with_map meta
param_map params body ap_args)
in
decr inline_depth;
inlined))
| _ -> normal ()
else normal ()
else normal ()
Expand Down
81 changes: 81 additions & 0 deletions tests/tests/src/inline_chain_test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Generated by ReScript, PLEASE EDIT WITH CARE

import * as Mocha from "mocha";
import * as Test_utils from "./test_utils.mjs";

function f0(x) {
return x + 1 | 0;
}

function f1(x) {
return (x + 1 | 0) + 1 | 0;
}

function f2(x) {
return ((x + 1 | 0) + 1 | 0) + 2 | 0;
}

function f3(x) {
return (((x + 1 | 0) + 1 | 0) + 2 | 0) + 3 | 0;
}

function f4(x) {
return ((((x + 1 | 0) + 1 | 0) + 2 | 0) + 3 | 0) + 4 | 0;
}

function f5(x) {
return (((((x + 1 | 0) + 1 | 0) + 2 | 0) + 3 | 0) + 4 | 0) + 5 | 0;
}

function f6(x) {
return ((((f1(x) + 2 | 0) + 3 | 0) + 4 | 0) + 5 | 0) + 6 | 0;
}

function g0(x) {
return (x << 1);
}

function g1(x) {
return ((x << 1) << 1);
}

function g2(x) {
return (((x << 1) << 1) << 1);
}

function g3(x) {
return ((((x << 1) << 1) << 1) << 1);
}

function g4(x) {
return ((((x << 1) << 1) << 1) << 1) + 1 | 0;
}

let zero = {
contents: 0
};

Mocha.describe("Inline_chain_test", () => {
Mocha.test("chain of small functions", () => {
Test_utils.eq("File \"inline_chain_test.res\", line 28, characters 7-14", (((f2(zero.contents) + 3 | 0) + 4 | 0) + 5 | 0) + 6 | 0, 22);
Test_utils.eq("File \"inline_chain_test.res\", line 29, characters 7-14", ((((zero.contents + 1 | 0) + 1 | 0) + 1 | 0) + 2 | 0) + 3 | 0, 8);
});
Mocha.test("chain of @inline functions", () => Test_utils.eq("File \"inline_chain_test.res\", line 32, characters 7-14", (((((zero.contents + 1 | 0) << 1) << 1) << 1) << 1) + 1 | 0, 17));
});

export {
f0,
f1,
f2,
f3,
f4,
f5,
f6,
g0,
g1,
g2,
g3,
g4,
zero,
}
/* Not a pure module */
34 changes: 34 additions & 0 deletions tests/tests/src/inline_chain_test.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Inlining a call can inline the functions that the inlined body calls, but
// only a few levels deep: in a chain of small functions that each call the
// previous one, a later function calls an earlier one instead of containing
// the whole chain. Functions marked @inline are still inlined all the way.

let f0 = x => x + 1
let f1 = x => f0(x) + 1
let f2 = x => f1(x) + 2
let f3 = x => f2(x) + 3
let f4 = x => f3(x) + 4
let f5 = x => f4(x) + 5
let f6 = x => f5(x) + 6

@inline let g0 = x => x * 2
@inline let g1 = x => g0(x) * 2
@inline let g2 = x => g1(x) * 2
@inline let g3 = x => g2(x) * 2
let g4 = x => g3(x) + 1

// Read through a ref so the calls below are not evaluated at compile time
let zero = ref(0)

open Mocha
open Test_utils

describe(__MODULE__, () => {
test("chain of small functions", () => {
eq(__LOC__, f6(zero.contents), 22)
eq(__LOC__, f3(zero.contents + 1), 8)
})
test("chain of @inline functions", () => {
eq(__LOC__, g4(zero.contents + 1), 17)
})
})
Loading