Skip to content

Commit 46ce8ee

Browse files
swernliidavis
andauthored
Fix out-of-memory in typeck on nested Ty::Arrow (#2336)
This change updates the `Ty::Arrow` enum variant to use `Rc` which allows sharing of instances of callable types within the type infrastructure. To allow mutability during type inference, the underlying `Arrow` struct now uses `RefCell` for it's contained type and relies on runtime borrow checking for mutability. Fixes #2302 --------- Co-authored-by: Ian Davis <[email protected]>
1 parent 026ccc3 commit 46ce8ee

File tree

17 files changed

+231
-122
lines changed

17 files changed

+231
-122
lines changed

compiler/qsc/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,7 @@ harness = false
8080
[[bench]]
8181
name = "rca"
8282
harness = false
83+
84+
[[bench]]
85+
name = "typeck"
86+
harness = false

compiler/qsc/benches/typeck.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use criterion::{criterion_group, criterion_main, Criterion};
5+
use indoc::indoc;
6+
use qsc::{
7+
interpret::Interpreter, LanguageFeatures, PackageType, SourceMap, TargetCapabilityFlags,
8+
};
9+
10+
pub fn deep_nested_callable_generics(c: &mut Criterion) {
11+
c.bench_function("Deeply nested callable generics", |b| {
12+
b.iter(move || {
13+
let sources = SourceMap::new(
14+
[("none".into(), "".into())],
15+
Some(
16+
indoc! {"{
17+
function swap<'T1, 'T2>(op : ('T1, 'T2) -> ('T2, 'T1), input: ('T1, 'T2)) : ('T2, 'T1) {
18+
op(input)
19+
}
20+
function nested_swaps() : Unit {
21+
let a = swap(swap(swap(swap(swap(_)))));
22+
}
23+
}"}
24+
.into(),
25+
),
26+
);
27+
let (std_id, store) = qsc::compile::package_store_with_stdlib(TargetCapabilityFlags::all());
28+
29+
assert!(Interpreter::new(
30+
sources,
31+
PackageType::Exe,
32+
TargetCapabilityFlags::all(),
33+
LanguageFeatures::default(),
34+
store,
35+
&[(std_id, None)],
36+
)
37+
.is_err(), "code should fail with type error");
38+
});
39+
});
40+
}
41+
42+
criterion_group!(benches, deep_nested_callable_generics);
43+
criterion_main!(benches);

compiler/qsc_frontend/src/closure.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use qsc_hir::{
1313
visit::{self, Visitor},
1414
};
1515
use rustc_hash::{FxHashMap, FxHashSet};
16-
use std::iter;
16+
use std::{iter, rc::Rc};
1717

1818
pub(super) struct Lambda {
1919
pub(super) kind: CallableKind,
@@ -162,27 +162,28 @@ pub(super) fn partial_app_block(
162162
callee: Expr,
163163
arg: Expr,
164164
app: PartialApp,
165-
arrow: Arrow,
165+
arrow: Rc<Arrow>,
166166
span: Span,
167167
) -> Block {
168168
let call = Expr {
169169
id: NodeId::default(),
170170
span,
171-
ty: (*arrow.output).clone(),
171+
ty: arrow.output.borrow().clone(),
172172
kind: ExprKind::Call(Box::new(callee), Box::new(arg)),
173173
};
174174
let lambda = Lambda {
175175
kind: arrow.kind,
176176
functors: arrow
177177
.functors
178+
.borrow()
178179
.expect_value("lambda type should have concrete functors"),
179180
input: app.input,
180181
body: call,
181182
};
182183
let closure = Expr {
183184
id: NodeId::default(),
184185
span,
185-
ty: Ty::Arrow(Box::new(arrow.clone())),
186+
ty: Ty::Arrow(arrow.clone()),
186187
kind: close(lambda),
187188
};
188189

@@ -195,7 +196,7 @@ pub(super) fn partial_app_block(
195196
Block {
196197
id: NodeId::default(),
197198
span,
198-
ty: Ty::Arrow(Box::new(arrow)),
199+
ty: Ty::Arrow(arrow),
199200
stmts,
200201
}
201202
}

compiler/qsc_frontend/src/compile/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
mod multiple_packages;
55

6-
use std::sync::Arc;
6+
use std::{rc::Rc, sync::Arc};
77

88
use super::{compile, longest_common_prefix, CompileUnit, Error, PackageStore, SourceMap};
99
use crate::compile::TargetCapabilityFlags;
@@ -356,7 +356,7 @@ fn insert_core_call() {
356356
let callee = Expr {
357357
id: NodeId::default(),
358358
span: Span::default(),
359-
ty: Ty::Arrow(Box::new(allocate_ty)),
359+
ty: Ty::Arrow(Rc::new(allocate_ty)),
360360
kind: ExprKind::Var(Res::Item(allocate.id), Vec::new()),
361361
};
362362

compiler/qsc_frontend/src/lower.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -739,7 +739,7 @@ impl With<'_> {
739739
ast::ExprKind::Block(block) => hir::ExprKind::Block(self.lower_block(block)),
740740
ast::ExprKind::Call(callee, arg) => match &ty {
741741
Ty::Arrow(arrow) if is_partial_app(arg) => hir::ExprKind::Block(
742-
self.lower_partial_app(callee, arg, (**arrow).clone(), expr.span),
742+
self.lower_partial_app(callee, arg, arrow.clone(), expr.span),
743743
),
744744
_ => hir::ExprKind::Call(
745745
Box::new(self.lower_expr(callee)),
@@ -779,6 +779,7 @@ impl With<'_> {
779779
let functors = if let Ty::Arrow(arrow) = &ty {
780780
arrow
781781
.functors
782+
.borrow()
782783
.expect_value("lambda type should have concrete functors")
783784
} else {
784785
FunctorSetValue::Empty
@@ -881,7 +882,7 @@ impl With<'_> {
881882
&mut self,
882883
callee: &ast::Expr,
883884
arg: &ast::Expr,
884-
arrow: Arrow,
885+
arrow: Rc<Arrow>,
885886
span: Span,
886887
) -> hir::Block {
887888
let callee = self.lower_expr(callee);

compiler/qsc_frontend/src/lower/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,7 +1356,7 @@ fn lambda_operation_empty_closure() {
13561356
Pat 7 [69-70] [Type Qubit]: Bind: Ident 8 [69-70] "q"
13571357
QubitInit 9 [73-80] [Type Qubit]: Single
13581358
Stmt 10 [90-95]: Expr: Expr 11 [90-95] [Type Unit]: Call:
1359-
Expr 12 [90-92] [Type (Qubit => Unit)]: Var: Local 3
1359+
Expr 12 [90-92] [Type (Qubit => Unit is Param<0>)]: Var: Local 3
13601360
Expr 13 [93-94] [Type Qubit]: Var: Local 8
13611361
adj: <none>
13621362
ctl: <none>
@@ -1436,7 +1436,7 @@ fn lambda_operation_closure() {
14361436
body: SpecDecl 9 [80-130]: Impl:
14371437
Block 10 [122-130] [Type Result]:
14381438
Stmt 11 [124-128]: Expr: Expr 12 [124-128] [Type Result]: Call:
1439-
Expr 13 [124-126] [Type (Unit => Result)]: Var: Local 8
1439+
Expr 13 [124-126] [Type (Unit => Result is Param<0>)]: Var: Local 8
14401440
Expr 14 [126-128] [Type Unit]: Unit
14411441
adj: <none>
14421442
ctl: <none>

compiler/qsc_frontend/src/typeck/convert.rs

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the MIT License.
33

44
//! Ascribe types to the AST and output HIR items. Put another way, converts the AST to the HIR.
5-
use std::rc::Rc;
5+
use std::{cell::RefCell, rc::Rc};
66

77
use crate::resolve::{self, Names};
88

@@ -61,11 +61,11 @@ pub(crate) fn ty_from_ast(
6161
let functors = functors
6262
.as_ref()
6363
.map_or(FunctorSetValue::Empty, |f| eval_functor_expr(f.as_ref()));
64-
let ty = Ty::Arrow(Box::new(Arrow {
64+
let ty = Ty::Arrow(Rc::new(Arrow {
6565
kind: callable_kind_from_ast(*kind),
66-
input: Box::new(input),
67-
output: Box::new(output),
68-
functors: FunctorSet::Value(functors),
66+
input: RefCell::new(input),
67+
output: RefCell::new(output),
68+
functors: RefCell::new(FunctorSet::Value(functors)),
6969
}));
7070
(ty, errors)
7171
}
@@ -157,9 +157,9 @@ pub(super) fn ast_ty_def_cons(
157157
let (input, errors) = ast_ty_def_base(names, def);
158158
let ty = Arrow {
159159
kind: hir::CallableKind::Function,
160-
input: Box::new(input),
161-
output: Box::new(Ty::Udt(ty_name.clone(), hir::Res::Item(id))),
162-
functors: FunctorSet::Value(FunctorSetValue::Empty),
160+
input: RefCell::new(input),
161+
output: RefCell::new(Ty::Udt(ty_name.clone(), hir::Res::Item(id))),
162+
functors: RefCell::new(FunctorSet::Value(FunctorSetValue::Empty)),
163163
};
164164
let scheme = Scheme::new(Vec::new(), Box::new(ty));
165165
(scheme, errors)
@@ -274,9 +274,9 @@ pub(super) fn scheme_for_ast_callable(
274274

275275
let ty = Arrow {
276276
kind,
277-
input: Box::new(input),
278-
output: Box::new(output),
279-
functors: FunctorSet::Value(ast_callable_functors(callable)),
277+
input: RefCell::new(input),
278+
output: RefCell::new(output),
279+
functors: RefCell::new(FunctorSet::Value(ast_callable_functors(callable))),
280280
};
281281

282282
(Scheme::new(type_parameters, Box::new(ty)), errors)
@@ -290,15 +290,24 @@ pub(crate) fn synthesize_functor_params(
290290
) -> Vec<HirTypeParameter> {
291291
match ty {
292292
Ty::Array(item) => synthesize_functor_params(next_param, item),
293-
Ty::Arrow(arrow) => match arrow.functors {
294-
FunctorSet::Value(functors) if arrow.kind == hir::CallableKind::Operation => {
295-
let param = HirTypeParameter::Functor(functors);
296-
arrow.functors = FunctorSet::Param(*next_param, functors);
297-
*next_param = next_param.successor();
298-
vec![param]
293+
Ty::Arrow(arrow) => {
294+
let functors = *arrow.functors.borrow();
295+
match functors {
296+
FunctorSet::Value(functors) if arrow.kind == hir::CallableKind::Operation => {
297+
let param = HirTypeParameter::Functor(functors);
298+
// This uses `RefCell::replace` to update the functor set within the reference counted arrow.
299+
// This should be safe as no other references to the functors should be held, but if they are
300+
// this will panic at runtime.
301+
// Note the `next_param` local binding is needed to ensure that the `functors` are not borrowed
302+
// while we are trying to replace them.
303+
let new_functors = FunctorSet::Param(*next_param, functors);
304+
arrow.functors.replace(new_functors);
305+
*next_param = next_param.successor();
306+
vec![param]
307+
}
308+
_ => Vec::new(),
299309
}
300-
_ => Vec::new(),
301-
},
310+
}
302311
Ty::Tuple(items) => items
303312
.iter_mut()
304313
.flat_map(|item| synthesize_functor_params(next_param, item))

0 commit comments

Comments
 (0)