Skip to content

Commit 51cb078

Browse files
authored
Support lowering of functions with variable binding parameters. (#6012)
Fix a crash when attempting to lower a function with a variable binding as a parameter. This is a narrowly-targeted fix, and not the right longer-term approach; more complex patterns as function parameters will still fail and likely crash.
1 parent 74a8d51 commit 51cb078

File tree

5 files changed

+159
-9
lines changed

5 files changed

+159
-9
lines changed

toolchain/lower/file_context.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,11 +327,21 @@ auto FileContext::BuildFunctionTypeInfo(const SemIR::Function& function,
327327
}
328328
for (auto param_pattern_id : llvm::concat<const SemIR::InstId>(
329329
implicit_param_patterns, param_patterns)) {
330+
// TODO: Handle a general pattern here, rather than assuming that each
331+
// parameter pattern contains at most one binding.
330332
auto param_pattern_info = SemIR::Function::GetParamPatternInfoFromPatternId(
331333
sem_ir(), param_pattern_id);
332334
if (!param_pattern_info) {
333335
continue;
334336
}
337+
// TODO: Use a more general mechanism to determine if the binding is a
338+
// reference binding.
339+
if (param_pattern_info->var_pattern_id.has_value()) {
340+
param_types.push_back(
341+
llvm::PointerType::get(llvm_context(), /*AddressSpace=*/0));
342+
param_inst_ids.push_back(param_pattern_id);
343+
continue;
344+
}
335345
auto param_type_id = ExtractScrutineeType(
336346
sem_ir(), SemIR::GetTypeOfInstInSpecific(sem_ir(), specific_id,
337347
param_pattern_info->inst_id));
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon
6+
//
7+
// AUTOUPDATE
8+
// TIP: To test this file alone, run:
9+
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/function/definition/var_param.carbon
10+
// TIP: To dump output, run:
11+
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/function/definition/var_param.carbon
12+
13+
class X {}
14+
15+
fn OneVar_i32(var n: i32) {}
16+
fn OneVar_X(var x: X) {}
17+
18+
fn TwoVars(var a: i32, var b: X) {}
19+
20+
fn VarThenLet(var a: i32, b: X) {}
21+
fn LetThenVar(a: i32, var b: X) {}
22+
23+
fn Call() {
24+
OneVar_i32(1);
25+
OneVar_X({});
26+
TwoVars(1, {});
27+
VarThenLet(1, {});
28+
LetThenVar(1, {});
29+
}
30+
31+
// CHECK:STDOUT: ; ModuleID = 'var_param.carbon'
32+
// CHECK:STDOUT: source_filename = "var_param.carbon"
33+
// CHECK:STDOUT:
34+
// CHECK:STDOUT: @X.val.loc16_13.2 = internal constant {} zeroinitializer
35+
// CHECK:STDOUT:
36+
// CHECK:STDOUT: define void @_COneVar_i32.Main(ptr %n) !dbg !4 {
37+
// CHECK:STDOUT: entry:
38+
// CHECK:STDOUT: ret void, !dbg !7
39+
// CHECK:STDOUT: }
40+
// CHECK:STDOUT:
41+
// CHECK:STDOUT: define void @_COneVar_X.Main(ptr %x) !dbg !8 {
42+
// CHECK:STDOUT: entry:
43+
// CHECK:STDOUT: ret void, !dbg !9
44+
// CHECK:STDOUT: }
45+
// CHECK:STDOUT:
46+
// CHECK:STDOUT: define void @_CTwoVars.Main(ptr %a, ptr %b) !dbg !10 {
47+
// CHECK:STDOUT: entry:
48+
// CHECK:STDOUT: ret void, !dbg !11
49+
// CHECK:STDOUT: }
50+
// CHECK:STDOUT:
51+
// CHECK:STDOUT: define void @_CVarThenLet.Main(ptr %a, ptr %b) !dbg !12 {
52+
// CHECK:STDOUT: entry:
53+
// CHECK:STDOUT: ret void, !dbg !13
54+
// CHECK:STDOUT: }
55+
// CHECK:STDOUT:
56+
// CHECK:STDOUT: define void @_CLetThenVar.Main(i32 %a, ptr %b) !dbg !14 {
57+
// CHECK:STDOUT: entry:
58+
// CHECK:STDOUT: ret void, !dbg !15
59+
// CHECK:STDOUT: }
60+
// CHECK:STDOUT:
61+
// CHECK:STDOUT: define void @_CCall.Main() !dbg !16 {
62+
// CHECK:STDOUT: entry:
63+
// CHECK:STDOUT: %.loc15_15.1.temp = alloca i32, align 4, !dbg !17
64+
// CHECK:STDOUT: %.loc16_13.1.temp = alloca {}, align 8, !dbg !18
65+
// CHECK:STDOUT: %.loc18_12.1.temp = alloca i32, align 4, !dbg !19
66+
// CHECK:STDOUT: %.loc18_24.1.temp = alloca {}, align 8, !dbg !20
67+
// CHECK:STDOUT: %.loc20_15.1.temp = alloca i32, align 4, !dbg !21
68+
// CHECK:STDOUT: %.loc27_18.2.temp = alloca {}, align 8, !dbg !22
69+
// CHECK:STDOUT: %.loc21_23.1.temp = alloca {}, align 8, !dbg !23
70+
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc15_15.1.temp), !dbg !17
71+
// CHECK:STDOUT: store i32 1, ptr %.loc15_15.1.temp, align 4, !dbg !17
72+
// CHECK:STDOUT: call void @_COneVar_i32.Main(ptr %.loc15_15.1.temp), !dbg !24
73+
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc16_13.1.temp), !dbg !18
74+
// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %.loc16_13.1.temp, ptr align 1 @X.val.loc16_13.2, i64 0, i1 false), !dbg !18
75+
// CHECK:STDOUT: call void @_COneVar_X.Main(ptr %.loc16_13.1.temp), !dbg !25
76+
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc18_12.1.temp), !dbg !19
77+
// CHECK:STDOUT: store i32 1, ptr %.loc18_12.1.temp, align 4, !dbg !19
78+
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc18_24.1.temp), !dbg !20
79+
// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %.loc18_24.1.temp, ptr align 1 @X.val.loc16_13.2, i64 0, i1 false), !dbg !20
80+
// CHECK:STDOUT: call void @_CTwoVars.Main(ptr %.loc18_12.1.temp, ptr %.loc18_24.1.temp), !dbg !26
81+
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc20_15.1.temp), !dbg !21
82+
// CHECK:STDOUT: store i32 1, ptr %.loc20_15.1.temp, align 4, !dbg !21
83+
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc27_18.2.temp), !dbg !22
84+
// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %.loc27_18.2.temp, ptr align 1 @X.val.loc16_13.2, i64 0, i1 false), !dbg !22
85+
// CHECK:STDOUT: call void @_CVarThenLet.Main(ptr %.loc20_15.1.temp, ptr %.loc27_18.2.temp), !dbg !27
86+
// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc21_23.1.temp), !dbg !23
87+
// CHECK:STDOUT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %.loc21_23.1.temp, ptr align 1 @X.val.loc16_13.2, i64 0, i1 false), !dbg !23
88+
// CHECK:STDOUT: call void @_CLetThenVar.Main(i32 1, ptr %.loc21_23.1.temp), !dbg !28
89+
// CHECK:STDOUT: ret void, !dbg !29
90+
// CHECK:STDOUT: }
91+
// CHECK:STDOUT:
92+
// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
93+
// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #0
94+
// CHECK:STDOUT:
95+
// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
96+
// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i64, i1 immarg) #1
97+
// CHECK:STDOUT:
98+
// CHECK:STDOUT: ; uselistorder directives
99+
// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 6, 5, 4, 3, 2, 1, 0 }
100+
// CHECK:STDOUT: uselistorder ptr @llvm.memcpy.p0.p0.i64, { 3, 2, 1, 0 }
101+
// CHECK:STDOUT:
102+
// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
103+
// CHECK:STDOUT: attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
104+
// CHECK:STDOUT:
105+
// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
106+
// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
107+
// CHECK:STDOUT:
108+
// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
109+
// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
110+
// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
111+
// CHECK:STDOUT: !3 = !DIFile(filename: "var_param.carbon", directory: "")
112+
// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "OneVar_i32", linkageName: "_COneVar_i32.Main", scope: null, file: !3, line: 15, type: !5, spFlags: DISPFlagDefinition, unit: !2)
113+
// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
114+
// CHECK:STDOUT: !6 = !{}
115+
// CHECK:STDOUT: !7 = !DILocation(line: 15, column: 1, scope: !4)
116+
// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "OneVar_X", linkageName: "_COneVar_X.Main", scope: null, file: !3, line: 16, type: !5, spFlags: DISPFlagDefinition, unit: !2)
117+
// CHECK:STDOUT: !9 = !DILocation(line: 16, column: 1, scope: !8)
118+
// CHECK:STDOUT: !10 = distinct !DISubprogram(name: "TwoVars", linkageName: "_CTwoVars.Main", scope: null, file: !3, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2)
119+
// CHECK:STDOUT: !11 = !DILocation(line: 18, column: 1, scope: !10)
120+
// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "VarThenLet", linkageName: "_CVarThenLet.Main", scope: null, file: !3, line: 20, type: !5, spFlags: DISPFlagDefinition, unit: !2)
121+
// CHECK:STDOUT: !13 = !DILocation(line: 20, column: 1, scope: !12)
122+
// CHECK:STDOUT: !14 = distinct !DISubprogram(name: "LetThenVar", linkageName: "_CLetThenVar.Main", scope: null, file: !3, line: 21, type: !5, spFlags: DISPFlagDefinition, unit: !2)
123+
// CHECK:STDOUT: !15 = !DILocation(line: 21, column: 1, scope: !14)
124+
// CHECK:STDOUT: !16 = distinct !DISubprogram(name: "Call", linkageName: "_CCall.Main", scope: null, file: !3, line: 23, type: !5, spFlags: DISPFlagDefinition, unit: !2)
125+
// CHECK:STDOUT: !17 = !DILocation(line: 15, column: 15, scope: !16)
126+
// CHECK:STDOUT: !18 = !DILocation(line: 16, column: 13, scope: !16)
127+
// CHECK:STDOUT: !19 = !DILocation(line: 18, column: 12, scope: !16)
128+
// CHECK:STDOUT: !20 = !DILocation(line: 18, column: 24, scope: !16)
129+
// CHECK:STDOUT: !21 = !DILocation(line: 20, column: 15, scope: !16)
130+
// CHECK:STDOUT: !22 = !DILocation(line: 27, column: 17, scope: !16)
131+
// CHECK:STDOUT: !23 = !DILocation(line: 21, column: 23, scope: !16)
132+
// CHECK:STDOUT: !24 = !DILocation(line: 24, column: 3, scope: !16)
133+
// CHECK:STDOUT: !25 = !DILocation(line: 25, column: 3, scope: !16)
134+
// CHECK:STDOUT: !26 = !DILocation(line: 26, column: 3, scope: !16)
135+
// CHECK:STDOUT: !27 = !DILocation(line: 27, column: 3, scope: !16)
136+
// CHECK:STDOUT: !28 = !DILocation(line: 28, column: 3, scope: !16)
137+
// CHECK:STDOUT: !29 = !DILocation(line: 23, column: 1, scope: !16)

toolchain/sem_ir/function.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ auto Function::GetParamPatternInfoFromPatternId(const File& sem_ir,
9898
auto inst = sem_ir.insts().Get(inst_id);
9999

100100
sem_ir.insts().TryUnwrap(inst, inst_id, &AddrPattern::inner_id);
101+
auto [var_pattern, var_pattern_id] =
102+
sem_ir.insts().TryUnwrap(inst, inst_id, &VarPattern::subpattern_id);
101103
auto [param_pattern, param_pattern_id] =
102104
sem_ir.insts().TryUnwrap(inst, inst_id, &AnyParamPattern::subpattern_id);
103105
if (!param_pattern) {
@@ -107,7 +109,8 @@ auto Function::GetParamPatternInfoFromPatternId(const File& sem_ir,
107109
auto binding_pattern = inst.As<AnyBindingPattern>();
108110
return {{.inst_id = param_pattern_id,
109111
.inst = *param_pattern,
110-
.entity_name_id = binding_pattern.entity_name_id}};
112+
.entity_name_id = binding_pattern.entity_name_id,
113+
.var_pattern_id = var_pattern_id}};
111114
}
112115

113116
auto Function::GetDeclaredReturnType(const File& file,

toolchain/sem_ir/function.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ struct Function : public EntityWithParamsBase,
9595
InstId inst_id;
9696
AnyParamPattern inst;
9797
EntityNameId entity_name_id;
98+
KnownInstId<VarPattern> var_pattern_id;
9899
};
99100

100101
auto Print(llvm::raw_ostream& out) const -> void {

toolchain/sem_ir/inst.h

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ class InstStore {
524524

525525
template <class InstT>
526526
struct GetAsWithIdResult {
527-
SemIR::KnownInstId<InstT> inst_id;
527+
KnownInstId<InstT> inst_id;
528528
InstT inst;
529529
};
530530

@@ -534,8 +534,7 @@ class InstStore {
534534
template <typename InstT>
535535
auto GetAsWithId(InstId inst_id) const -> GetAsWithIdResult<InstT> {
536536
auto inst = GetAs<InstT>(inst_id);
537-
return {.inst_id = SemIR::KnownInstId<InstT>::UnsafeMake(inst_id),
538-
.inst = inst};
537+
return {.inst_id = KnownInstId<InstT>::UnsafeMake(inst_id), .inst = inst};
539538
}
540539

541540
// Returns the requested instruction, if it is of that type, along with the
@@ -548,8 +547,8 @@ class InstStore {
548547
if (!inst) {
549548
return std::nullopt;
550549
}
551-
return {{.inst_id = SemIR::KnownInstId<InstT>::UnsafeMake(inst_id),
552-
.inst = *inst}};
550+
return {
551+
{.inst_id = KnownInstId<InstT>::UnsafeMake(inst_id), .inst = *inst}};
553552
}
554553

555554
// Attempts to convert the given instruction to the type that contains
@@ -559,14 +558,14 @@ class InstStore {
559558
template <typename InstT, typename InstIdT>
560559
requires std::derived_from<InstIdT, InstId>
561560
auto TryUnwrap(Inst& inst, InstId& inst_id, InstIdT InstT::* member) const
562-
-> std::pair<std::optional<InstT>, InstId> {
561+
-> std::pair<std::optional<InstT>, KnownInstId<InstT>> {
563562
if (auto wrapped_inst = inst.TryAs<InstT>()) {
564-
auto wrapped_inst_id = inst_id;
563+
auto wrapped_inst_id = KnownInstId<InstT>::UnsafeMake(inst_id);
565564
inst_id = (*wrapped_inst).*member;
566565
inst = Get(inst_id);
567566
return {wrapped_inst, wrapped_inst_id};
568567
}
569-
return {std::nullopt, InstId::None};
568+
return {std::nullopt, KnownInstId<InstT>::None};
570569
}
571570

572571
// Returns a resolved LocId, which will point to a parse node, an import, or

0 commit comments

Comments
 (0)