Skip to content

Commit 766af91

Browse files
committed
Give an AllocId to ConstValue::Slice.
1 parent fc1a4c5 commit 766af91

File tree

59 files changed

+574
-151
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+574
-151
lines changed

compiler/rustc_codegen_cranelift/src/constant.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,7 @@ pub(crate) fn codegen_const_value<'tcx>(
196196
.offset_i64(fx, i64::try_from(offset.bytes()).unwrap()),
197197
layout,
198198
),
199-
ConstValue::Slice { data, meta } => {
200-
let alloc_id = fx.tcx.reserve_and_set_memory_alloc(data);
199+
ConstValue::Slice { alloc_id, meta, phantom: _ } => {
201200
let ptr = pointer_for_allocation(fx, alloc_id).get_addr(fx);
202201
let len = fx.bcx.ins().iconst(fx.pointer_type, meta as i64);
203202
CValue::by_val_pair(ptr, len, layout)

compiler/rustc_codegen_ssa/src/mir/operand.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,11 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
100100
OperandValue::Immediate(llval)
101101
}
102102
ConstValue::ZeroSized => return OperandRef::zero_sized(layout),
103-
ConstValue::Slice { data, meta } => {
103+
ConstValue::Slice { alloc_id, meta, phantom: _ } => {
104104
let Abi::ScalarPair(a_scalar, _) = layout.abi else {
105105
bug!("from_const: invalid ScalarPair layout: {:#?}", layout);
106106
};
107-
let a = Scalar::from_pointer(
108-
Pointer::new(bx.tcx().reserve_and_set_memory_alloc(data).into(), Size::ZERO),
109-
&bx.tcx(),
110-
);
107+
let a = Scalar::from_pointer(Pointer::new(alloc_id.into(), Size::ZERO), &bx.tcx());
111108
let a_llval = bx.scalar_to_backend(
112109
a,
113110
a_scalar,

compiler/rustc_const_eval/src/const_eval/eval_queries.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,9 @@ pub(super) fn op_to_const<'tcx>(
210210
// We know `offset` is relative to the allocation, so we can use `into_parts`.
211211
let (prov, offset) = a.to_pointer(ecx).expect(msg).into_parts();
212212
let alloc_id = prov.expect(msg).alloc_id();
213-
let data = ecx.tcx.global_alloc(alloc_id).unwrap_memory();
214213
assert!(offset == abi::Size::ZERO, "{}", msg);
215214
let meta = b.to_target_usize(ecx).expect(msg);
216-
ConstValue::Slice { data, meta }
215+
ConstValue::Slice { alloc_id, meta, phantom: std::marker::PhantomData }
217216
}
218217
Immediate::Uninit => bug!("`Uninit` is not a valid value for {}", op.layout.ty),
219218
},

compiler/rustc_const_eval/src/interpret/intrinsics.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ use rustc_middle::ty::{Ty, TyCtxt};
1010
use rustc_middle::{
1111
mir::{
1212
self,
13-
interpret::{
14-
Allocation, ConstAllocation, GlobalId, InterpResult, PointerArithmetic, Scalar,
15-
},
13+
interpret::{AllocId, GlobalId, InterpResult, PointerArithmetic, Scalar},
1614
BinOp, ConstValue, NonDivergingIntrinsic,
1715
},
1816
ty::layout::TyAndLayout,
@@ -28,10 +26,11 @@ use super::{
2826
use crate::fluent_generated as fluent;
2927

3028
/// Directly returns an `Allocation` containing an absolute path representation of the given type.
31-
pub(crate) fn alloc_type_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> ConstAllocation<'tcx> {
29+
pub(crate) fn alloc_type_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> (AllocId, u64) {
3230
let path = crate::util::type_name(tcx, ty);
33-
let alloc = Allocation::from_bytes_byte_aligned_immutable(path.into_bytes());
34-
tcx.mk_const_alloc(alloc)
31+
let bytes = path.into_bytes();
32+
let len = bytes.len().try_into().unwrap();
33+
(tcx.allocate_bytes(bytes), len)
3534
}
3635

3736
/// The logic for all nullary intrinsics is implemented here. These intrinsics don't get evaluated
@@ -47,8 +46,8 @@ pub(crate) fn eval_nullary_intrinsic<'tcx>(
4746
Ok(match name {
4847
sym::type_name => {
4948
ensure_monomorphic_enough(tcx, tp_ty)?;
50-
let alloc = alloc_type_name(tcx, tp_ty);
51-
ConstValue::Slice { data: alloc, meta: alloc.inner().size().bytes() }
49+
let (alloc_id, len) = alloc_type_name(tcx, tp_ty);
50+
ConstValue::Slice { alloc_id, meta: len, phantom: std::marker::PhantomData }
5251
}
5352
sym::needs_drop => {
5453
ensure_monomorphic_enough(tcx, tp_ty)?;

compiler/rustc_const_eval/src/interpret/operand.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -780,9 +780,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
780780
}
781781
mir::ConstValue::Scalar(x) => adjust_scalar(x)?.into(),
782782
mir::ConstValue::ZeroSized => Immediate::Uninit,
783-
mir::ConstValue::Slice { data, meta } => {
783+
mir::ConstValue::Slice { alloc_id, meta, phantom: _ } => {
784784
// This is const data, no mutation allowed.
785-
let alloc_id = self.tcx.reserve_and_set_memory_alloc(data);
786785
let ptr = Pointer::new(CtfeProvenance::from(alloc_id).as_immutable(), Size::ZERO);
787786
Immediate::new_slice(self.global_base_pointer(ptr)?.into(), meta, self)
788787
}

compiler/rustc_middle/src/mir/consts.rs

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use rustc_session::{config::RemapPathScopeComponents, RemapFileNameExt};
55
use rustc_span::{Span, DUMMY_SP};
66
use rustc_target::abi::{HasDataLayout, Size};
77

8-
use crate::mir::interpret::{alloc_range, AllocId, ConstAllocation, ErrorHandled, Scalar};
8+
use crate::mir::interpret::{alloc_range, AllocId, ErrorHandled, Scalar};
99
use crate::mir::{pretty_print_const_value, Promoted};
1010
use crate::ty::print::with_no_trimmed_paths;
1111
use crate::ty::GenericArgsRef;
@@ -28,8 +28,8 @@ pub struct ConstAlloc<'tcx> {
2828

2929
/// Represents a constant value in Rust. `Scalar` and `Slice` are optimizations for
3030
/// array length computations, enum discriminants and the pattern matching logic.
31-
#[derive(Copy, Clone, Debug, Eq, PartialEq, TyEncodable, TyDecodable, Hash)]
32-
#[derive(HashStable, Lift)]
31+
#[derive(Copy, Clone, Debug, Eq, PartialEq, TyEncodable, TyDecodable, Lift, Hash)]
32+
#[derive(HashStable)]
3333
pub enum ConstValue<'tcx> {
3434
/// Used for types with `layout::abi::Scalar` ABI.
3535
///
@@ -48,10 +48,11 @@ pub enum ConstValue<'tcx> {
4848
Slice {
4949
/// The allocation storing the slice contents.
5050
/// This always points to the beginning of the allocation.
51-
data: ConstAllocation<'tcx>,
51+
alloc_id: AllocId,
5252
/// The metadata field of the reference.
5353
/// This is a "target usize", so we use `u64` as in the interpreter.
5454
meta: u64,
55+
phantom: std::marker::PhantomData<&'tcx ()>,
5556
},
5657

5758
/// A value not representable by the other variants; needs to be stored in-memory.
@@ -73,7 +74,7 @@ pub enum ConstValue<'tcx> {
7374
#[cfg(all(any(target_arch = "x86_64", target_arch = "aarch64"), target_pointer_width = "64"))]
7475
static_assert_size!(ConstValue<'_>, 24);
7576

76-
impl<'tcx> ConstValue<'tcx> {
77+
impl ConstValue<'_> {
7778
#[inline]
7879
pub fn try_to_scalar(&self) -> Option<Scalar> {
7980
match *self {
@@ -94,11 +95,11 @@ impl<'tcx> ConstValue<'tcx> {
9495
self.try_to_scalar_int()?.try_into().ok()
9596
}
9697

97-
pub fn try_to_target_usize(&self, tcx: TyCtxt<'tcx>) -> Option<u64> {
98+
pub fn try_to_target_usize(&self, tcx: TyCtxt<'_>) -> Option<u64> {
9899
self.try_to_scalar_int()?.try_to_target_usize(tcx).ok()
99100
}
100101

101-
pub fn try_to_bits_for_ty(
102+
pub fn try_to_bits_for_ty<'tcx>(
102103
&self,
103104
tcx: TyCtxt<'tcx>,
104105
param_env: ty::ParamEnv<'tcx>,
@@ -125,12 +126,15 @@ impl<'tcx> ConstValue<'tcx> {
125126
}
126127

127128
/// Must only be called on constants of type `&str` or `&[u8]`!
128-
pub fn try_get_slice_bytes_for_diagnostics(&self, tcx: TyCtxt<'tcx>) -> Option<&'tcx [u8]> {
129-
let (data, start, end) = match self {
129+
pub fn try_get_slice_bytes_for_diagnostics<'tcx>(
130+
&self,
131+
tcx: TyCtxt<'tcx>,
132+
) -> Option<&'tcx [u8]> {
133+
let (alloc_id, start, len) = match self {
130134
ConstValue::Scalar(_) | ConstValue::ZeroSized => {
131135
bug!("`try_get_slice_bytes` on non-slice constant")
132136
}
133-
&ConstValue::Slice { data, meta } => (data, 0, meta),
137+
&ConstValue::Slice { alloc_id, meta, phantom: _ } => (alloc_id, 0, meta),
134138
&ConstValue::Indirect { alloc_id, offset } => {
135139
// The reference itself is stored behind an indirection.
136140
// Load the reference, and then load the actual slice contents.
@@ -162,26 +166,29 @@ impl<'tcx> ConstValue<'tcx> {
162166
}
163167
// Non-empty slice, must have memory. We know this is a relative pointer.
164168
let (inner_prov, offset) = ptr.into_parts();
165-
let data = tcx.global_alloc(inner_prov?.alloc_id()).unwrap_memory();
166-
(data, offset.bytes(), offset.bytes() + len)
169+
(inner_prov?.alloc_id(), offset.bytes(), offset.bytes() + len)
167170
}
168171
};
169172

173+
let data = tcx.global_alloc(alloc_id).unwrap_memory();
174+
170175
// This is for diagnostics only, so we are okay to use `inspect_with_uninit_and_ptr_outside_interpreter`.
171176
let start = start.try_into().unwrap();
172-
let end = end.try_into().unwrap();
177+
let end = start + usize::try_from(len).unwrap();
173178
Some(data.inner().inspect_with_uninit_and_ptr_outside_interpreter(start..end))
174179
}
175180

176181
/// Check if a constant may contain provenance information. This is used by MIR opts.
177182
/// Can return `true` even if there is no provenance.
178-
pub fn may_have_provenance(&self, tcx: TyCtxt<'tcx>, size: Size) -> bool {
183+
pub fn may_have_provenance(&self, tcx: TyCtxt<'_>, size: Size) -> bool {
179184
match *self {
180185
ConstValue::ZeroSized | ConstValue::Scalar(Scalar::Int(_)) => return false,
181186
ConstValue::Scalar(Scalar::Ptr(..)) => return true,
182187
// It's hard to find out the part of the allocation we point to;
183188
// just conservatively check everything.
184-
ConstValue::Slice { data, meta: _ } => !data.inner().provenance().ptrs().is_empty(),
189+
ConstValue::Slice { alloc_id, meta: _, phantom: _ } => {
190+
!tcx.global_alloc(alloc_id).unwrap_memory().inner().provenance().ptrs().is_empty()
191+
}
185192
ConstValue::Indirect { alloc_id, offset } => !tcx
186193
.global_alloc(alloc_id)
187194
.unwrap_memory()
@@ -424,9 +431,8 @@ impl<'tcx> Const<'tcx> {
424431
/// taking into account even pointer identity tests.
425432
pub fn is_deterministic(&self) -> bool {
426433
// Some constants may generate fresh allocations for pointers they contain,
427-
// so using the same constant twice can yield two different results:
428-
// - valtrees purposefully generate new allocations
429-
// - ConstValue::Slice also generate new allocations
434+
// so using the same constant twice can yield two different results.
435+
// Notably, valtrees purposefully generate new allocations.
430436
match self {
431437
Const::Ty(c) => match c.kind() {
432438
ty::ConstKind::Param(..) => true,
@@ -444,11 +450,11 @@ impl<'tcx> Const<'tcx> {
444450
| ty::ConstKind::Placeholder(..) => bug!(),
445451
},
446452
Const::Unevaluated(..) => false,
447-
// If the same slice appears twice in the MIR, we cannot guarantee that we will
448-
// give the same `AllocId` to the data.
449-
Const::Val(ConstValue::Slice { .. }, _) => false,
450453
Const::Val(
451-
ConstValue::ZeroSized | ConstValue::Scalar(_) | ConstValue::Indirect { .. },
454+
ConstValue::Slice { .. }
455+
| ConstValue::ZeroSized
456+
| ConstValue::Scalar(_)
457+
| ConstValue::Indirect { .. },
452458
_,
453459
) => true,
454460
}

compiler/rustc_middle/src/mir/pretty.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,11 +1360,7 @@ pub fn write_allocations<'tcx>(
13601360
}
13611361
ConstValue::Scalar(interpret::Scalar::Int { .. }) => Either::Right(std::iter::empty()),
13621362
ConstValue::ZeroSized => Either::Right(std::iter::empty()),
1363-
ConstValue::Slice { .. } => {
1364-
// `u8`/`str` slices, shouldn't contain pointers that we want to print.
1365-
Either::Right(std::iter::empty())
1366-
}
1367-
ConstValue::Indirect { alloc_id, .. } => {
1363+
ConstValue::Slice { alloc_id, .. } | ConstValue::Indirect { alloc_id, .. } => {
13681364
// FIXME: we don't actually want to print all of these, since some are printed nicely directly as values inline in MIR.
13691365
// Really we'd want `pretty_print_const_value` to decide which allocations to print, instead of having a separate visitor.
13701366
Either::Left(std::iter::once(alloc_id))

compiler/rustc_middle/src/ty/context.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ use rustc_type_ir::TyKind::*;
7272
use rustc_type_ir::WithCachedTypeInfo;
7373
use rustc_type_ir::{CollectAndApply, Interner, TypeFlags};
7474

75-
use std::borrow::Borrow;
75+
use std::borrow::{Borrow, Cow};
7676
use std::cmp::Ordering;
7777
use std::fmt;
7878
use std::hash::{Hash, Hasher};
@@ -866,7 +866,7 @@ impl<'tcx> TyCtxt<'tcx> {
866866
}
867867

868868
/// Allocates a read-only byte or string literal for `mir::interpret`.
869-
pub fn allocate_bytes(self, bytes: &[u8]) -> interpret::AllocId {
869+
pub fn allocate_bytes<'a>(self, bytes: impl Into<Cow<'a, [u8]>>) -> interpret::AllocId {
870870
// Create an allocation that just contains these bytes.
871871
let alloc = interpret::Allocation::from_bytes_byte_aligned_immutable(bytes);
872872
let alloc = self.mk_const_alloc(alloc);

compiler/rustc_middle/src/ty/structural_impls.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use rustc_target::abi::TyAndLayout;
1616
use rustc_type_ir::{ConstKind, DebugWithInfcx, InferCtxtLike, WithInfcx};
1717

1818
use std::fmt::{self, Debug};
19+
use std::marker::PhantomData;
1920

2021
use super::print::PrettyPrinter;
2122
use super::{GenericArg, GenericArgKind, Region};
@@ -459,6 +460,13 @@ TrivialTypeTraversalAndLiftImpls! {
459460
///////////////////////////////////////////////////////////////////////////
460461
// Lift implementations
461462

463+
impl<'tcx> Lift<'tcx> for PhantomData<&()> {
464+
type Lifted = PhantomData<&'tcx ()>;
465+
fn lift_to_tcx(self, _: TyCtxt<'tcx>) -> Option<Self::Lifted> {
466+
Some(PhantomData)
467+
}
468+
}
469+
462470
impl<'tcx, T: Lift<'tcx>> Lift<'tcx> for Option<T> {
463471
type Lifted = Option<T::Lifted>;
464472
fn lift_to_tcx(self, tcx: TyCtxt<'tcx>) -> Option<Self::Lifted> {

compiler/rustc_mir_build/src/build/expr/as_constant.rs

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
use crate::build::{parse_float_into_constval, Builder};
44
use rustc_ast as ast;
55
use rustc_middle::mir;
6-
use rustc_middle::mir::interpret::{Allocation, LitToConstError, LitToConstInput, Scalar};
6+
use rustc_middle::mir::interpret::{LitToConstError, LitToConstInput, Scalar};
77
use rustc_middle::mir::*;
88
use rustc_middle::thir::*;
99
use rustc_middle::ty::{
1010
self, CanonicalUserType, CanonicalUserTypeAnnotation, TyCtxt, UserTypeAnnotationIndex,
1111
};
1212
use rustc_target::abi::Size;
13+
use std::marker::PhantomData;
1314

1415
impl<'a, 'tcx> Builder<'a, 'tcx> {
1516
/// Compile `expr`, yielding a compile-time constant. Assumes that
@@ -124,27 +125,39 @@ fn lit_to_mir_constant<'tcx>(
124125

125126
let value = match (lit, &ty.kind()) {
126127
(ast::LitKind::Str(s, _), ty::Ref(_, inner_ty, _)) if inner_ty.is_str() => {
127-
let s = s.as_str();
128-
let allocation = Allocation::from_bytes_byte_aligned_immutable(s.as_bytes());
129-
let allocation = tcx.mk_const_alloc(allocation);
130-
ConstValue::Slice { data: allocation, meta: allocation.inner().size().bytes() }
128+
let s = s.as_str().as_bytes();
129+
let len = s.len();
130+
let allocation = tcx.allocate_bytes(s);
131+
ConstValue::Slice {
132+
alloc_id: allocation,
133+
meta: len.try_into().unwrap(),
134+
phantom: PhantomData,
135+
}
131136
}
132137
(ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _))
133138
if matches!(inner_ty.kind(), ty::Slice(_)) =>
134139
{
135-
let allocation = Allocation::from_bytes_byte_aligned_immutable(data as &[u8]);
136-
let allocation = tcx.mk_const_alloc(allocation);
137-
ConstValue::Slice { data: allocation, meta: allocation.inner().size().bytes() }
140+
let len = data.len();
141+
let allocation = tcx.allocate_bytes(&**data);
142+
ConstValue::Slice {
143+
alloc_id: allocation,
144+
meta: len.try_into().unwrap(),
145+
phantom: PhantomData,
146+
}
138147
}
139148
(ast::LitKind::ByteStr(data, _), ty::Ref(_, inner_ty, _)) if inner_ty.is_array() => {
140-
let id = tcx.allocate_bytes(data);
149+
let id = tcx.allocate_bytes(&**data);
141150
ConstValue::Scalar(Scalar::from_pointer(id.into(), &tcx))
142151
}
143152
(ast::LitKind::CStr(data, _), ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Adt(def, _) if Some(def.did()) == tcx.lang_items().c_str()) =>
144153
{
145-
let allocation = Allocation::from_bytes_byte_aligned_immutable(data as &[u8]);
146-
let allocation = tcx.mk_const_alloc(allocation);
147-
ConstValue::Slice { data: allocation, meta: allocation.inner().size().bytes() }
154+
let len = data.len();
155+
let allocation = tcx.allocate_bytes(&**data);
156+
ConstValue::Slice {
157+
alloc_id: allocation,
158+
meta: len.try_into().unwrap(),
159+
phantom: PhantomData,
160+
}
148161
}
149162
(ast::LitKind::Byte(n), ty::Uint(ty::UintTy::U8)) => {
150163
ConstValue::Scalar(Scalar::from_uint(*n, Size::from_bytes(1)))

compiler/rustc_monomorphize/src/collector.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,10 +1496,8 @@ fn collect_const_value<'tcx>(
14961496
collect_alloc(tcx, ptr.provenance.alloc_id(), output)
14971497
}
14981498
mir::ConstValue::Indirect { alloc_id, .. } => collect_alloc(tcx, alloc_id, output),
1499-
mir::ConstValue::Slice { data, meta: _ } => {
1500-
for &prov in data.inner().provenance().ptrs().values() {
1501-
collect_alloc(tcx, prov.alloc_id(), output);
1502-
}
1499+
mir::ConstValue::Slice { alloc_id, meta: _, phantom: _ } => {
1500+
collect_alloc(tcx, alloc_id, output);
15031501
}
15041502
_ => {}
15051503
}

compiler/rustc_smir/src/rustc_smir/alloc.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,7 @@ pub fn try_new_allocation<'tcx>(
5959
.align;
6060
new_empty_allocation(align.abi)
6161
}
62-
ConstValue::Slice { data, meta } => {
63-
let alloc_id = tables.tcx.reserve_and_set_memory_alloc(data);
62+
ConstValue::Slice { alloc_id, meta, phantom: _ } => {
6463
let ptr = Pointer::new(alloc_id.into(), rustc_target::abi::Size::ZERO);
6564
let scalar_ptr = rustc_middle::mir::interpret::Scalar::from_pointer(ptr, &tables.tcx);
6665
let scalar_meta =

tests/mir-opt/building/issue_101867.main.built.after.mir

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,7 @@ fn main() -> () {
8080
resume;
8181
}
8282
}
83+
84+
ALLOC0 (size: 14, align: 1) {
85+
65 78 70 6c 69 63 69 74 20 70 61 6e 69 63 │ explicit panic
86+
}

tests/mir-opt/building/match/sort_candidates.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ fn constant_eq(s: &str, b: bool) -> u32 {
77

88
// CHECK-LABEL: fn constant_eq(
99
// CHECK: bb0: {
10-
// CHECK: [[a:_.*]] = const "a";
11-
// CHECK-NOT: {{_.*}} = const "a";
10+
// CHECK: const "a"
11+
// CHECK-NOT: const "a"
1212
match (s, b) {
1313
("a", _) if true => 1,
1414
("b", true) => 2,

tests/mir-opt/building/storage_live_dead_in_statics.XXX.built.after.mir

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,7 @@ static XXX: &Foo = {
198198
return;
199199
}
200200
}
201+
202+
ALLOC0 (size: 2, align: 1) {
203+
68 69 │ hi
204+
}

0 commit comments

Comments
 (0)