forked from OSchip/llvm-project
[Attributor] Introduce a helper do deal with constant type mismatches
If we simplify values we sometimes end up with type mismatches. If the value is a constant we can often cast it though to still allow propagation. The logic is now put into a helper and it replaces some ad hoc things we did before. This also introduces the AA namespace for abstract attribute related functions and types.
This commit is contained in:
parent
55e9c28212
commit
6caea8a7fa
|
@ -131,6 +131,15 @@ class AAManager;
|
|||
class AAResults;
|
||||
class Function;
|
||||
|
||||
/// Abstract Attribute helper functions.
|
||||
namespace AA {
|
||||
/// Try to convert \p V to type \p Ty without introducing new instructions. If
|
||||
/// this is not possible return `nullptr`. Note: this function basically knows
|
||||
/// how to cast various constants.
|
||||
Value *getWithType(Value &V, Type &Ty);
|
||||
|
||||
} // namespace AA
|
||||
|
||||
/// The value passed to the line option that defines the maximal initialization
|
||||
/// chain length.
|
||||
extern unsigned MaxInitializationChainLength;
|
||||
|
|
|
@ -159,6 +159,24 @@ ChangeStatus llvm::operator&(ChangeStatus L, ChangeStatus R) {
|
|||
}
|
||||
///}
|
||||
|
||||
Value *AA::getWithType(Value &V, Type &Ty) {
|
||||
if (V.getType() == &Ty)
|
||||
return &V;
|
||||
if (isa<UndefValue>(V))
|
||||
return UndefValue::get(&Ty);
|
||||
if (auto *C = dyn_cast<Constant>(&V)) {
|
||||
if (C->isNullValue())
|
||||
return Constant::getNullValue(&Ty);
|
||||
if (C->getType()->isPointerTy() && Ty.isPointerTy())
|
||||
return ConstantExpr::getPointerCast(C, &Ty);
|
||||
if (C->getType()->isIntegerTy() && Ty.isIntegerTy())
|
||||
return ConstantExpr::getTrunc(C, &Ty, /* OnlyIfReduced */ true);
|
||||
if (C->getType()->isFloatingPointTy() && Ty.isFloatingPointTy())
|
||||
return ConstantExpr::getFPTrunc(C, &Ty, /* OnlyIfReduced */ true);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Return true if \p New is equal or worse than \p Old.
|
||||
static bool isEqualOrWorse(const Attribute &New, const Attribute &Old) {
|
||||
if (!Old.isIntAttribute())
|
||||
|
|
|
@ -4631,12 +4631,13 @@ struct AAValueSimplifyImpl : AAValueSimplify {
|
|||
auto *C = SimplifiedAssociatedValue.hasValue()
|
||||
? dyn_cast<Constant>(SimplifiedAssociatedValue.getValue())
|
||||
: UndefValue::get(V.getType());
|
||||
if (C) {
|
||||
if (C && C != &V) {
|
||||
Value *NewV = AA::getWithType(*C, *V.getType());
|
||||
// We can replace the AssociatedValue with the constant.
|
||||
if (!V.user_empty() && &V != C && V.getType() == C->getType()) {
|
||||
LLVM_DEBUG(dbgs() << "[ValueSimplify] " << V << " -> " << *C
|
||||
if (!V.user_empty() && &V != C && NewV) {
|
||||
LLVM_DEBUG(dbgs() << "[ValueSimplify] " << V << " -> " << *NewV
|
||||
<< " :: " << *this << "\n");
|
||||
if (A.changeValueAfterManifest(V, *C))
|
||||
if (A.changeValueAfterManifest(V, *NewV))
|
||||
Changed = ChangeStatus::CHANGED;
|
||||
}
|
||||
}
|
||||
|
@ -4778,23 +4779,23 @@ struct AAValueSimplifyReturned : AAValueSimplifyImpl {
|
|||
auto *C = SimplifiedAssociatedValue.hasValue()
|
||||
? dyn_cast<Constant>(SimplifiedAssociatedValue.getValue())
|
||||
: UndefValue::get(V.getType());
|
||||
if (C) {
|
||||
if (C && C != &V) {
|
||||
auto PredForReturned =
|
||||
[&](Value &V, const SmallSetVector<ReturnInst *, 4> &RetInsts) {
|
||||
// We can replace the AssociatedValue with the constant.
|
||||
if (&V == C || V.getType() != C->getType() || isa<UndefValue>(V))
|
||||
if (&V == C || isa<UndefValue>(V))
|
||||
return true;
|
||||
|
||||
for (ReturnInst *RI : RetInsts) {
|
||||
if (RI->getFunction() != getAnchorScope())
|
||||
continue;
|
||||
auto *RC = C;
|
||||
if (RC->getType() != RI->getReturnValue()->getType())
|
||||
RC = ConstantExpr::getPointerCast(
|
||||
RC, RI->getReturnValue()->getType());
|
||||
LLVM_DEBUG(dbgs() << "[ValueSimplify] " << V << " -> " << *RC
|
||||
Value *NewV =
|
||||
AA::getWithType(*C, *RI->getReturnValue()->getType());
|
||||
if (!NewV)
|
||||
continue;
|
||||
LLVM_DEBUG(dbgs() << "[ValueSimplify] " << V << " -> " << *NewV
|
||||
<< " in " << *RI << " :: " << *this << "\n");
|
||||
if (A.changeUseAfterManifest(RI->getOperandUse(0), *RC))
|
||||
if (A.changeUseAfterManifest(RI->getOperandUse(0), *NewV))
|
||||
Changed = ChangeStatus::CHANGED;
|
||||
}
|
||||
return true;
|
||||
|
@ -4994,9 +4995,10 @@ struct AAValueSimplifyCallSiteArgument : AAValueSimplifyFloating {
|
|||
Use &U = cast<CallBase>(&getAnchorValue())
|
||||
->getArgOperandUse(getCallSiteArgNo());
|
||||
// We can replace the AssociatedValue with the constant.
|
||||
if (&V != C && V.getType() == C->getType()) {
|
||||
if (A.changeUseAfterManifest(U, *C))
|
||||
Changed = ChangeStatus::CHANGED;
|
||||
if (&V != C) {
|
||||
if (Value *NewV = AA::getWithType(*C, *V.getType()))
|
||||
if (A.changeUseAfterManifest(U, *NewV))
|
||||
Changed = ChangeStatus::CHANGED;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,9 +43,8 @@ define void @run() {
|
|||
; IS__CGSCC____-LABEL: define {{[^@]+}}@run
|
||||
; IS__CGSCC____-SAME: () #[[ATTR0:[0-9]+]] {
|
||||
; IS__CGSCC____-NEXT: entry:
|
||||
; IS__CGSCC____-NEXT: [[A_CAST:%.*]] = bitcast %struct.Foo* @a to i32*
|
||||
; IS__CGSCC____-NEXT: [[TMP0:%.*]] = load i32, i32* [[A_CAST]], align 8
|
||||
; IS__CGSCC____-NEXT: [[A_0_1:%.*]] = getelementptr [[STRUCT_FOO:%.*]], %struct.Foo* @a, i32 0, i32 1
|
||||
; IS__CGSCC____-NEXT: [[TMP0:%.*]] = load i32, i32* getelementptr inbounds ([[STRUCT_FOO:%.*]], %struct.Foo* @a, i32 0, i32 0), align 8
|
||||
; IS__CGSCC____-NEXT: [[A_0_1:%.*]] = getelementptr [[STRUCT_FOO]], %struct.Foo* @a, i32 0, i32 1
|
||||
; IS__CGSCC____-NEXT: [[TMP1:%.*]] = load i64, i64* [[A_0_1]], align 8
|
||||
; IS__CGSCC____-NEXT: unreachable
|
||||
;
|
||||
|
|
|
@ -59,11 +59,10 @@ define internal i32 @vfu2(%struct.MYstr* byval(%struct.MYstr) align 4 %u) nounwi
|
|||
; IS__TUNIT_OPM-NEXT: entry:
|
||||
; IS__TUNIT_OPM-NEXT: [[TMP0:%.*]] = getelementptr [[STRUCT_MYSTR]], %struct.MYstr* @mystr, i32 0, i32 1
|
||||
; IS__TUNIT_OPM-NEXT: [[TMP1:%.*]] = load i32, i32* [[TMP0]], align 4
|
||||
; IS__TUNIT_OPM-NEXT: [[TMP2:%.*]] = getelementptr [[STRUCT_MYSTR]], %struct.MYstr* @mystr, i32 0, i32 0
|
||||
; IS__TUNIT_OPM-NEXT: [[TMP3:%.*]] = load i8, i8* [[TMP2]], align 8
|
||||
; IS__TUNIT_OPM-NEXT: [[TMP4:%.*]] = zext i8 [[TMP3]] to i32
|
||||
; IS__TUNIT_OPM-NEXT: [[TMP5:%.*]] = add i32 [[TMP4]], [[TMP1]]
|
||||
; IS__TUNIT_OPM-NEXT: ret i32 [[TMP5]]
|
||||
; IS__TUNIT_OPM-NEXT: [[TMP2:%.*]] = load i8, i8* getelementptr inbounds ([[STRUCT_MYSTR]], %struct.MYstr* @mystr, i32 0, i32 0), align 8
|
||||
; IS__TUNIT_OPM-NEXT: [[TMP3:%.*]] = zext i8 [[TMP2]] to i32
|
||||
; IS__TUNIT_OPM-NEXT: [[TMP4:%.*]] = add i32 [[TMP3]], [[TMP1]]
|
||||
; IS__TUNIT_OPM-NEXT: ret i32 [[TMP4]]
|
||||
;
|
||||
; IS__TUNIT_NPM: Function Attrs: nofree nosync nounwind readonly willreturn
|
||||
; IS__TUNIT_NPM-LABEL: define {{[^@]+}}@vfu2
|
||||
|
@ -76,11 +75,10 @@ define internal i32 @vfu2(%struct.MYstr* byval(%struct.MYstr) align 4 %u) nounwi
|
|||
; IS__TUNIT_NPM-NEXT: store i32 [[TMP1]], i32* [[U_PRIV_0_1]], align 4
|
||||
; IS__TUNIT_NPM-NEXT: [[TMP2:%.*]] = getelementptr [[STRUCT_MYSTR]], %struct.MYstr* @mystr, i32 0, i32 1
|
||||
; IS__TUNIT_NPM-NEXT: [[TMP3:%.*]] = load i32, i32* [[TMP2]], align 4
|
||||
; IS__TUNIT_NPM-NEXT: [[TMP4:%.*]] = getelementptr [[STRUCT_MYSTR]], %struct.MYstr* @mystr, i32 0, i32 0
|
||||
; IS__TUNIT_NPM-NEXT: [[TMP5:%.*]] = load i8, i8* [[TMP4]], align 8
|
||||
; IS__TUNIT_NPM-NEXT: [[TMP6:%.*]] = zext i8 [[TMP5]] to i32
|
||||
; IS__TUNIT_NPM-NEXT: [[TMP7:%.*]] = add i32 [[TMP6]], [[TMP3]]
|
||||
; IS__TUNIT_NPM-NEXT: ret i32 [[TMP7]]
|
||||
; IS__TUNIT_NPM-NEXT: [[TMP4:%.*]] = load i8, i8* getelementptr inbounds ([[STRUCT_MYSTR]], %struct.MYstr* @mystr, i32 0, i32 0), align 8
|
||||
; IS__TUNIT_NPM-NEXT: [[TMP5:%.*]] = zext i8 [[TMP4]] to i32
|
||||
; IS__TUNIT_NPM-NEXT: [[TMP6:%.*]] = add i32 [[TMP5]], [[TMP3]]
|
||||
; IS__TUNIT_NPM-NEXT: ret i32 [[TMP6]]
|
||||
;
|
||||
; IS__CGSCC____: Function Attrs: nofree norecurse nosync nounwind readonly willreturn
|
||||
; IS__CGSCC____-LABEL: define {{[^@]+}}@vfu2
|
||||
|
@ -88,11 +86,10 @@ define internal i32 @vfu2(%struct.MYstr* byval(%struct.MYstr) align 4 %u) nounwi
|
|||
; IS__CGSCC____-NEXT: entry:
|
||||
; IS__CGSCC____-NEXT: [[TMP0:%.*]] = getelementptr [[STRUCT_MYSTR:%.*]], %struct.MYstr* @mystr, i32 0, i32 1
|
||||
; IS__CGSCC____-NEXT: [[TMP1:%.*]] = load i32, i32* [[TMP0]], align 4
|
||||
; IS__CGSCC____-NEXT: [[TMP2:%.*]] = getelementptr [[STRUCT_MYSTR]], %struct.MYstr* @mystr, i32 0, i32 0
|
||||
; IS__CGSCC____-NEXT: [[TMP3:%.*]] = load i8, i8* [[TMP2]], align 8
|
||||
; IS__CGSCC____-NEXT: [[TMP4:%.*]] = zext i8 [[TMP3]] to i32
|
||||
; IS__CGSCC____-NEXT: [[TMP5:%.*]] = add i32 [[TMP4]], [[TMP1]]
|
||||
; IS__CGSCC____-NEXT: ret i32 [[TMP5]]
|
||||
; IS__CGSCC____-NEXT: [[TMP2:%.*]] = load i8, i8* getelementptr inbounds ([[STRUCT_MYSTR]], %struct.MYstr* @mystr, i32 0, i32 0), align 8
|
||||
; IS__CGSCC____-NEXT: [[TMP3:%.*]] = zext i8 [[TMP2]] to i32
|
||||
; IS__CGSCC____-NEXT: [[TMP4:%.*]] = add i32 [[TMP3]], [[TMP1]]
|
||||
; IS__CGSCC____-NEXT: ret i32 [[TMP4]]
|
||||
;
|
||||
entry:
|
||||
%0 = getelementptr %struct.MYstr, %struct.MYstr* %u, i32 0, i32 1 ; <i32*> [#uses=1]
|
||||
|
@ -245,9 +242,8 @@ define i32 @unions_v2() nounwind {
|
|||
; IS__CGSCC_NPM-LABEL: define {{[^@]+}}@unions_v2
|
||||
; IS__CGSCC_NPM-SAME: () #[[ATTR1]] {
|
||||
; IS__CGSCC_NPM-NEXT: entry:
|
||||
; IS__CGSCC_NPM-NEXT: [[MYSTR_CAST1:%.*]] = bitcast %struct.MYstr* @mystr to i8*
|
||||
; IS__CGSCC_NPM-NEXT: [[TMP0:%.*]] = load i8, i8* [[MYSTR_CAST1]], align 8
|
||||
; IS__CGSCC_NPM-NEXT: [[MYSTR_0_12:%.*]] = getelementptr [[STRUCT_MYSTR:%.*]], %struct.MYstr* @mystr, i32 0, i32 1
|
||||
; IS__CGSCC_NPM-NEXT: [[TMP0:%.*]] = load i8, i8* getelementptr inbounds ([[STRUCT_MYSTR:%.*]], %struct.MYstr* @mystr, i32 0, i32 0), align 8
|
||||
; IS__CGSCC_NPM-NEXT: [[MYSTR_0_12:%.*]] = getelementptr [[STRUCT_MYSTR]], %struct.MYstr* @mystr, i32 0, i32 1
|
||||
; IS__CGSCC_NPM-NEXT: [[TMP1:%.*]] = load i32, i32* [[MYSTR_0_12]], align 8
|
||||
; IS__CGSCC_NPM-NEXT: [[RESULT:%.*]] = call i32 @vfu2_v2(i8 [[TMP0]], i32 [[TMP1]]) #[[ATTR3:[0-9]+]]
|
||||
; IS__CGSCC_NPM-NEXT: ret i32 [[RESULT]]
|
||||
|
|
|
@ -15,19 +15,11 @@ define i64 @fn2() {
|
|||
; IS__TUNIT____-NEXT: [[CALL2:%.*]] = call i64 @fn1(i64 undef) #[[ATTR0]], !range [[RNG0:![0-9]+]]
|
||||
; IS__TUNIT____-NEXT: ret i64 [[CALL2]]
|
||||
;
|
||||
; IS__CGSCC_OPM: Function Attrs: nofree norecurse nosync nounwind readnone willreturn
|
||||
; IS__CGSCC_OPM-LABEL: define {{[^@]+}}@fn2
|
||||
; IS__CGSCC_OPM-SAME: () #[[ATTR0:[0-9]+]] {
|
||||
; IS__CGSCC_OPM-NEXT: entry:
|
||||
; IS__CGSCC_OPM-NEXT: [[CALL2:%.*]] = call i64 @fn1(i64 undef) #[[ATTR1:[0-9]+]]
|
||||
; IS__CGSCC_OPM-NEXT: ret i64 [[CALL2]]
|
||||
;
|
||||
; IS__CGSCC_NPM: Function Attrs: nofree norecurse nosync nounwind readnone willreturn
|
||||
; IS__CGSCC_NPM-LABEL: define {{[^@]+}}@fn2
|
||||
; IS__CGSCC_NPM-SAME: () #[[ATTR0:[0-9]+]] {
|
||||
; IS__CGSCC_NPM-NEXT: entry:
|
||||
; IS__CGSCC_NPM-NEXT: [[CALL2:%.*]] = call i64 @fn1(i64 undef) #[[ATTR1:[0-9]+]], !range [[RNG0:![0-9]+]]
|
||||
; IS__CGSCC_NPM-NEXT: ret i64 [[CALL2]]
|
||||
; IS__CGSCC____: Function Attrs: nofree norecurse nosync nounwind readnone willreturn
|
||||
; IS__CGSCC____-LABEL: define {{[^@]+}}@fn2
|
||||
; IS__CGSCC____-SAME: () #[[ATTR0:[0-9]+]] {
|
||||
; IS__CGSCC____-NEXT: entry:
|
||||
; IS__CGSCC____-NEXT: ret i64 undef
|
||||
;
|
||||
entry:
|
||||
%conv = sext i32 undef to i64
|
||||
|
@ -48,7 +40,7 @@ define i64 @fn2b(i32 %arg) {
|
|||
;
|
||||
; IS__CGSCC____: Function Attrs: nofree norecurse nosync nounwind readnone willreturn
|
||||
; IS__CGSCC____-LABEL: define {{[^@]+}}@fn2b
|
||||
; IS__CGSCC____-SAME: (i32 [[ARG:%.*]]) #[[ATTR0:[0-9]+]] {
|
||||
; IS__CGSCC____-SAME: (i32 [[ARG:%.*]]) #[[ATTR0]] {
|
||||
; IS__CGSCC____-NEXT: entry:
|
||||
; IS__CGSCC____-NEXT: [[CONV:%.*]] = sext i32 [[ARG]] to i64
|
||||
; IS__CGSCC____-NEXT: [[DIV:%.*]] = sdiv i64 8, [[CONV]]
|
||||
|
|
|
@ -26,21 +26,16 @@ define i32* @func1() {
|
|||
; CHECK: Function Attrs: nofree nosync nounwind readnone willreturn
|
||||
; CHECK-LABEL: define {{[^@]+}}@func1
|
||||
; CHECK-SAME: () #[[ATTR0]] {
|
||||
; CHECK-NEXT: [[PTR:%.*]] = call i32* @func1a() #[[ATTR0]]
|
||||
; CHECK-NEXT: ret i32* [[PTR]]
|
||||
; CHECK-NEXT: ret i32* getelementptr inbounds ([1 x i32], [1 x i32]* @var1, i32 0, i32 0)
|
||||
;
|
||||
%ptr = call i32* @func1a([1 x i32]* @var1)
|
||||
ret i32* %ptr
|
||||
}
|
||||
|
||||
; UTC_ARGS: --disable
|
||||
; CHECK-LABEL: define internal noundef nonnull align 4 dereferenceable(4) i32* @func1a()
|
||||
; CHECK-NEXT: ret i32* getelementptr inbounds ([1 x i32], [1 x i32]* @var1, i32 0, i32 0)
|
||||
define internal i32* @func1a([1 x i32]* %arg) {
|
||||
%ptr = getelementptr inbounds [1 x i32], [1 x i32]* %arg, i64 0, i64 0
|
||||
ret i32* %ptr
|
||||
}
|
||||
; UTC_ARGS: --enable
|
||||
|
||||
define internal void @func2a(i32* %0) {
|
||||
; CHECK: Function Attrs: nofree nosync nounwind willreturn writeonly
|
||||
|
|
|
@ -86,7 +86,7 @@ entry:
|
|||
; CHECK-NOT: nounwind
|
||||
; CHECK-NEXT: define
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: %call3 = call i32 (i8*, ...) @printf(i8* noundef nonnull dereferenceable(18) getelementptr inbounds ([18 x i8], [18 x i8]* @"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@", i64 0, i64 0))
|
||||
; CHECK-NEXT: %call3 = call i32 (i8*, ...) @printf(i8* noundef nonnull dereferenceable(18) getelementptr inbounds ([18 x i8], [18 x i8]* @"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@", i32 0, i32 0))
|
||||
; CHECK-NEXT: call void @"?overflow@@YAXXZ_may_throw"()
|
||||
; CHECK-NEXT: unreachable
|
||||
%call3 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@", i64 0, i64 0))
|
||||
|
|
|
@ -82,7 +82,7 @@ entry:
|
|||
; CHECK-NOT: nounwind
|
||||
; CHECK-NEXT: define
|
||||
; CHECK-NEXT: entry:
|
||||
; CHECK-NEXT: %call3 = call i32 (i8*, ...) @printf(i8* noundef nonnull dereferenceable(18) getelementptr inbounds ([18 x i8], [18 x i8]* @"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@", i64 0, i64 0))
|
||||
; CHECK-NEXT: %call3 = call i32 (i8*, ...) @printf(i8* noundef nonnull dereferenceable(18) getelementptr inbounds ([18 x i8], [18 x i8]* @"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@", i32 0, i32 0))
|
||||
; CHECK-NEXT: call void @"?overflow@@YAXXZ_may_throw"()
|
||||
; CHECK-NEXT: unreachable
|
||||
%call3 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([18 x i8], [18 x i8]* @"??_C@_0BC@NKPAGFFJ@Exception?5caught?6?$AA@", i64 0, i64 0))
|
||||
|
|
|
@ -623,15 +623,13 @@ define internal i8*@test_byval2(%struct.X* byval(%struct.X) %a) {
|
|||
; IS__TUNIT____: Function Attrs: nofree nosync nounwind readonly willreturn
|
||||
; IS__TUNIT____-LABEL: define {{[^@]+}}@test_byval2
|
||||
; IS__TUNIT____-SAME: () #[[ATTR4:[0-9]+]] {
|
||||
; IS__TUNIT____-NEXT: [[G0:%.*]] = getelementptr [[STRUCT_X:%.*]], %struct.X* @S, i32 0, i32 0
|
||||
; IS__TUNIT____-NEXT: [[L:%.*]] = load i8*, i8** [[G0]], align 8
|
||||
; IS__TUNIT____-NEXT: [[L:%.*]] = load i8*, i8** getelementptr inbounds ([[STRUCT_X:%.*]], %struct.X* @S, i32 0, i32 0), align 8
|
||||
; IS__TUNIT____-NEXT: ret i8* [[L]]
|
||||
;
|
||||
; IS__CGSCC____: Function Attrs: nofree norecurse nosync nounwind readonly willreturn
|
||||
; IS__CGSCC____-LABEL: define {{[^@]+}}@test_byval2
|
||||
; IS__CGSCC____-SAME: () #[[ATTR3:[0-9]+]] {
|
||||
; IS__CGSCC____-NEXT: [[G0:%.*]] = getelementptr [[STRUCT_X:%.*]], %struct.X* @S, i32 0, i32 0
|
||||
; IS__CGSCC____-NEXT: [[L:%.*]] = load i8*, i8** [[G0]], align 8
|
||||
; IS__CGSCC____-NEXT: [[L:%.*]] = load i8*, i8** getelementptr inbounds ([[STRUCT_X:%.*]], %struct.X* @S, i32 0, i32 0), align 8
|
||||
; IS__CGSCC____-NEXT: ret i8* [[L]]
|
||||
;
|
||||
%g0 = getelementptr %struct.X, %struct.X* %a, i32 0, i32 0
|
||||
|
|
Loading…
Reference in New Issue