Skip to content

Commit 48e069c

Browse files
committed
Canonicalize imported witness blocks
1 parent e32190a commit 48e069c

File tree

4 files changed

+130
-4
lines changed

4 files changed

+130
-4
lines changed

toolchain/check/import_ref.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3101,12 +3101,17 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
31013101
return ResolveResult::Retry();
31023102
}
31033103

3104+
auto witnesses_block_id = SemIR::InstBlockId::None;
3105+
if (inst.witnesses_block_id.has_value()) {
3106+
witnesses_block_id =
3107+
GetCanonicalWitnessesBlock(resolver.local_ir(), witnesses);
3108+
}
3109+
31043110
return ResolveResult::Deduplicated<SemIR::FacetValue>(
31053111
resolver,
31063112
{.type_id = resolver.local_types().GetTypeIdForTypeConstantId(type_id),
31073113
.type_inst_id = type_inst_id,
3108-
.witnesses_block_id = GetLocalCanonicalInstBlockId(
3109-
resolver, inst.witnesses_block_id, witnesses)});
3114+
.witnesses_block_id = witnesses_block_id});
31103115
}
31113116

31123117
static auto TryResolveTypedInst(ImportRefResolver& resolver,
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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/full.carbon
6+
//
7+
// This test is producing a case where interface ordering will differ between
8+
// interfaces.carbon and use.carbon, testing that witnesses are maintained in a
9+
// canonical order. This checks a regression where the imported ordering would
10+
// be incorrectly used, resulting in witnesses getting swapped and an opaque
11+
// error for the `SimpleOp` lookup because `Empty`'s table is empty.
12+
//
13+
// The IR is generally not significant, but the important difference is as
14+
// follows (if these become the same, it means the test is no longer effective):
15+
//
16+
// SET-CHECK-SUBSET
17+
// CHECK:STDOUT: --- interfaces.carbon
18+
// CHECK:STDOUT: %facet_type: type = facet_type <@SimpleOp & @Empty> [concrete]
19+
// CHECK:STDOUT: --- use.carbon
20+
// CHECK:STDOUT: %facet_type: type = facet_type <@Empty & @SimpleOp> [concrete]
21+
//
22+
// This check does not strictly exclude extra facet types, although that should
23+
// create different instruction names. We also anticipate diagnostics should be
24+
// produced if there's a significant error.
25+
//
26+
// NOAUTOUPDATE
27+
// TIP: To test this file alone, run:
28+
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/import_canonical_witnesses.carbon
29+
// TIP: To dump output, run:
30+
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/import_canonical_witnesses.carbon
31+
32+
33+
// --- interfaces.carbon
34+
//@include-in-dumps
35+
library "[[@TEST_NAME]]";
36+
37+
// An interface with a trivial function and blanket impl for pointers.
38+
interface SimpleOp {
39+
fn Op[self: Self]();
40+
}
41+
42+
impl forall [T:! type] T as SimpleOp {
43+
fn Op[self: Self]() {}
44+
}
45+
46+
// An empty interface with blanket impl for pointers.
47+
interface Empty {}
48+
impl forall [T:! type] T as Empty {}
49+
50+
// Class with a use of `SimpleOp.Op`.
51+
class InterfaceWrap(T:! SimpleOp & Empty) {
52+
fn UseSimpleOp[self: Self](value:! T) {
53+
value.(SimpleOp.Op)();
54+
}
55+
}
56+
57+
// Interface combining the above for an indirect `SimpleOp` use through `C`,
58+
// particularly use an associated constant.
59+
interface CombinedFacet {
60+
let T:! SimpleOp & Empty;
61+
fn GetInterfaceWrap[self: Self]() -> InterfaceWrap(T);
62+
}
63+
64+
// A wrapper providing `CombinedFacet`.
65+
class TypeWrap(T:! type) {
66+
impl as CombinedFacet where .T = T {
67+
fn GetInterfaceWrap[self: Self]() -> InterfaceWrap(T) {
68+
return {};
69+
}
70+
}
71+
}
72+
73+
// --- use.carbon
74+
//@include-in-dumps
75+
library "[[@TEST_NAME]]";
76+
import library "interfaces";
77+
78+
class C {}
79+
80+
fn F(t: TypeWrap(C), c:! C) {
81+
t.(CombinedFacet.GetInterfaceWrap)().UseSimpleOp(c);
82+
}

toolchain/sem_ir/facet_type_info.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
#include <tuple>
88

9+
#include "toolchain/sem_ir/file.h"
910
#include "toolchain/sem_ir/ids.h"
11+
#include "toolchain/sem_ir/typed_insts.h"
1012

1113
namespace Carbon::SemIR {
1214

@@ -232,4 +234,36 @@ IdentifiedFacetType::IdentifiedFacetType(
232234
SortAndDeduplicate(required_interfaces_, RequiredLess);
233235
}
234236

237+
auto GetCanonicalWitnessesBlock(File& sem_ir,
238+
llvm::SmallVector<SemIR::InstId>& witnesses)
239+
-> SemIR::InstBlockId {
240+
// Small blocks don't need to be sorted.
241+
if (witnesses.size() <= 1) {
242+
return sem_ir.inst_blocks().AddCanonical(witnesses);
243+
}
244+
245+
llvm::SmallVector<std::pair<SpecificInterface, SemIR::InstId>> sortable;
246+
sortable.reserve(witnesses.size());
247+
248+
// Produce the sorted order based on the witness's SpecificInterface.
249+
for (auto witness_id : witnesses) {
250+
auto witness = sem_ir.insts().GetAs<SemIR::LookupImplWitness>(witness_id);
251+
sortable.push_back(
252+
{sem_ir.specific_interfaces().Get(witness.query_specific_interface_id),
253+
witness_id});
254+
}
255+
llvm::sort(sortable, [](auto& lhs, auto& rhs) {
256+
return ImplsLess(lhs.first, rhs.first);
257+
});
258+
259+
// Update the original list with the new order (reusing to avoid an
260+
// allocation).
261+
for (auto [witness_id, sortable_entry] :
262+
llvm::zip_equal(witnesses, sortable)) {
263+
witness_id = sortable_entry.second;
264+
}
265+
266+
return sem_ir.inst_blocks().AddCanonical(witnesses);
267+
}
268+
235269
} // namespace Carbon::SemIR

toolchain/sem_ir/facet_type_info.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
namespace Carbon::SemIR {
1717

18+
class File;
19+
1820
#define CARBON_BUILTIN_CONSTRAINT_MASK(X) \
1921
/* Verifies types can use the builtin `type.destroy`. */ \
2022
X(TypeCanDestroy)
@@ -211,8 +213,11 @@ inline auto CarbonHashValue(const FacetTypeInfo& value, uint64_t seed)
211213
return static_cast<HashCode>(hasher);
212214
}
213215

214-
// Declared:
215-
// (Carbon::HashCode) (value_ = 12557349131240970624)
216+
// Given an array of witnesses, sorts them to match the FacetTypeInfo ordering
217+
// and returns the resulting block ID.
218+
auto GetCanonicalWitnessesBlock(File& sem_ir,
219+
llvm::SmallVector<SemIR::InstId>& witnesses)
220+
-> SemIR::InstBlockId;
216221

217222
} // namespace Carbon::SemIR
218223

0 commit comments

Comments
 (0)