422 lines · cpp
1//===- DXILResourceTest.cpp - Unit tests for DXILResource -----------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "llvm/Analysis/DXILResource.h"10#include "llvm/IR/Constants.h"11#include "llvm/IR/DataLayout.h"12#include "llvm/IR/GlobalVariable.h"13#include "llvm/IR/Module.h"14#include "gtest/gtest.h"15 16using namespace llvm;17using namespace dxil;18 19namespace {20// Helper to succinctly build resource shaped metadata for tests.21struct MDBuilder {22 LLVMContext &Context;23 Type *Int32Ty;24 Type *Int1Ty;25 26 MDBuilder(LLVMContext &Context, Type *Int32Ty, Type *Int1Ty)27 : Context(Context), Int32Ty(Int32Ty), Int1Ty(Int1Ty) {}28 29 Metadata *toMD(unsigned int V) {30 return ConstantAsMetadata::get(31 Constant::getIntegerValue(Int32Ty, APInt(32, V)));32 }33 Metadata *toMD(int V) { return toMD(static_cast<unsigned int>(V)); }34 Metadata *toMD(bool V) {35 return ConstantAsMetadata::get(36 Constant::getIntegerValue(Int32Ty, APInt(1, V)));37 }38 Metadata *toMD(Value *V) { return ValueAsMetadata::get(V); }39 Metadata *toMD(const char *V) { return MDString::get(Context, V); }40 Metadata *toMD(StringRef V) { return MDString::get(Context, V); }41 Metadata *toMD(std::nullptr_t V) { return nullptr; }42 Metadata *toMD(MDTuple *V) { return V; }43 44 template <typename... Ts> MDTuple *get(Ts... Vs) {45 std::array<Metadata *, sizeof...(Vs)> MDs{toMD(std::forward<Ts>(Vs))...};46 return MDNode::get(Context, MDs);47 }48};49 50testing::AssertionResult MDTupleEq(const char *LHSExpr, const char *RHSExpr,51 MDTuple *LHS, MDTuple *RHS) {52 if (LHS == RHS)53 return testing::AssertionSuccess();54 std::string LHSRepr, RHSRepr;55 raw_string_ostream LHSS(LHSRepr), RHSS(RHSRepr);56 LHS->printTree(LHSS);57 RHS->printTree(RHSS);58 59 return testing::AssertionFailure() << "Expected equality:\n"60 << " " << LHSExpr << "\n"61 << "Which is:\n"62 << " " << LHSRepr << "\n\n"63 << " " << RHSExpr << "\n"64 << "Which is:\n"65 << " " << RHSRepr;66}67 68#define EXPECT_MDEQ(X, Y) EXPECT_PRED_FORMAT2(MDTupleEq, X, Y)69 70#define EXPECT_PROPS_EQ(X, Y, Z) \71 EXPECT_EQ(X, (std::pair<uint32_t, uint32_t>{Y, Z}))72 73} // namespace74 75TEST(DXILResource, AnnotationsAndMetadata) {76 // TODO: How am I supposed to get this?77 DataLayout DL("e-m:e-p:32:32-i1:32-i8:8-i16:16-i32:32-i64:64-f16:16-f32:32-"78 "f64:64-n8:16:32:64-v96:32");79 80 LLVMContext Context;81 Module M("AnnotationsAndMetadata", Context);82 M.setDataLayout(DL);83 84 Type *Int1Ty = Type::getInt1Ty(Context);85 Type *Int8Ty = Type::getInt8Ty(Context);86 Type *Int32Ty = Type::getInt32Ty(Context);87 Type *FloatTy = Type::getFloatTy(Context);88 Type *DoubleTy = Type::getDoubleTy(Context);89 Type *Floatx4Ty = FixedVectorType::get(FloatTy, 4);90 Type *Floatx3Ty = FixedVectorType::get(FloatTy, 3);91 Type *Int32x2Ty = FixedVectorType::get(Int32Ty, 2);92 93 MDBuilder TestMD(Context, Int32Ty, Int1Ty);94 95 // ByteAddressBuffer Buffer;96 {97 ResourceTypeInfo RTI(llvm::TargetExtType::get(98 Context, "dx.RawBuffer", Int8Ty, {/*IsWriteable=*/0, /*IsROV=*/0}));99 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::SRV);100 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::RawBuffer);101 102 ResourceInfo RI(103 /*RecordID=*/0, /*Space=*/0, /*LowerBound=*/0, /*Size=*/1,104 RTI.getHandleTy(), "Buffer");105 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());106 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x0000000bU, 0U);107 EXPECT_MDEQ(RI.getAsMetadata(M, RTI),108 TestMD.get(0, GV, "Buffer", 0, 0, 1, 11, 0, nullptr));109 }110 111 // RWByteAddressBuffer BufferOut : register(u3, space2);112 {113 ResourceTypeInfo RTI(llvm::TargetExtType::get(114 Context, "dx.RawBuffer", Int8Ty, {/*IsWriteable=*/1, /*IsROV=*/0}));115 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::UAV);116 EXPECT_EQ(RTI.getUAV().IsROV, false);117 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::RawBuffer);118 119 ResourceInfo RI(120 /*RecordID=*/1, /*Space=*/2, /*LowerBound=*/3, /*Size=*/1,121 RTI.getHandleTy(), "BufferOut");122 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());123 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x0000100bU, 0U);124 EXPECT_MDEQ(RI.getAsMetadata(M, RTI),125 TestMD.get(1, GV, "BufferOut", 2, 3, 1, 11, false, false, false,126 nullptr));127 EXPECT_EQ(RI.GloballyCoherent, false);128 EXPECT_EQ(RI.hasCounter(), false);129 EXPECT_EQ(RI.CounterDirection, ResourceCounterDirection::Unknown);130 }131 132 // struct BufType0 { int i; float f; double d; };133 // StructuredBuffer<BufType0> Buffer0 : register(t0);134 {135 StructType *BufType0 =136 StructType::create(Context, {Int32Ty, FloatTy, DoubleTy}, "BufType0");137 ResourceTypeInfo RTI(llvm::TargetExtType::get(138 Context, "dx.RawBuffer", BufType0, {/*IsWriteable=*/0, /*IsROV=*/0}));139 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::SRV);140 ASSERT_EQ(RTI.isStruct(), true);141 EXPECT_EQ(RTI.getStruct(DL).Stride, 16u);142 EXPECT_EQ(RTI.getStruct(DL).AlignLog2, Log2(Align(8)));143 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::StructuredBuffer);144 145 ResourceInfo RI(146 /*RecordID=*/0, /*Space=*/0, /*LowerBound=*/0, /*Size=*/1,147 RTI.getHandleTy(), "Buffer0");148 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());149 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x0000030cU, 0x00000010U);150 EXPECT_MDEQ(RI.getAsMetadata(M, RTI), TestMD.get(0, GV, "Buffer0", 0, 0, 1,151 12, 0, TestMD.get(1, 16)));152 }153 154 // StructuredBuffer<float3> Buffer1 : register(t1);155 {156 ResourceTypeInfo RTI(llvm::TargetExtType::get(157 Context, "dx.RawBuffer", Floatx3Ty, {/*IsWriteable=*/0, /*IsROV=*/0}));158 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::SRV);159 ASSERT_EQ(RTI.isStruct(), true);160 EXPECT_EQ(RTI.getStruct(DL).Stride, 12u);161 EXPECT_EQ(RTI.getStruct(DL).AlignLog2, 0u);162 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::StructuredBuffer);163 164 ResourceInfo RI(165 /*RecordID=*/1, /*Space=*/0, /*LowerBound=*/1, /*Size=*/1,166 RTI.getHandleTy(), "Buffer1");167 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());168 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x0000000cU, 0x0000000cU);169 EXPECT_MDEQ(RI.getAsMetadata(M, RTI), TestMD.get(1, GV, "Buffer1", 0, 1, 1,170 12, 0, TestMD.get(1, 12)));171 }172 173 // Texture2D<float4> ColorMapTexture : register(t2);174 {175 ResourceTypeInfo RTI(llvm::TargetExtType::get(176 Context, "dx.Texture", Floatx4Ty,177 {/*IsWriteable=*/0, /*IsROV=*/0, /*IsSigned=*/0,178 llvm::to_underlying(ResourceKind::Texture2D)}));179 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::SRV);180 ASSERT_EQ(RTI.isTyped(), true);181 EXPECT_EQ(RTI.getTyped().ElementTy, ElementType::F32);182 EXPECT_EQ(RTI.getTyped().ElementCount, 4u);183 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::Texture2D);184 185 ResourceInfo RI(186 /*RecordID=*/2, /*Space=*/0, /*LowerBound=*/2, /*Size=*/1,187 RTI.getHandleTy(), "ColorMapTexture");188 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());189 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x00000002U, 0x00000409U);190 EXPECT_MDEQ(191 RI.getAsMetadata(M, RTI),192 TestMD.get(2, GV, "ColorMapTexture", 0, 2, 1, 2, 0, TestMD.get(0, 9)));193 }194 195 // Texture2DMS<float, 8> DepthBuffer : register(t0);196 {197 ResourceTypeInfo RTI(llvm::TargetExtType::get(198 Context, "dx.MSTexture", FloatTy,199 {/*IsWriteable=*/0, /*SampleCount=*/8,200 /*IsSigned=*/0, llvm::to_underlying(ResourceKind::Texture2DMS)}));201 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::SRV);202 ASSERT_EQ(RTI.isTyped(), true);203 EXPECT_EQ(RTI.getTyped().ElementTy, ElementType::F32);204 EXPECT_EQ(RTI.getTyped().ElementCount, 1u);205 ASSERT_EQ(RTI.isMultiSample(), true);206 EXPECT_EQ(RTI.getMultiSampleCount(), 8u);207 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::Texture2DMS);208 209 ResourceInfo RI(210 /*RecordID=*/0, /*Space=*/0, /*LowerBound=*/0, /*Size=*/1,211 RTI.getHandleTy(), "DepthBuffer");212 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());213 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x00000003U, 0x00080109U);214 EXPECT_MDEQ(215 RI.getAsMetadata(M, RTI),216 TestMD.get(0, GV, "DepthBuffer", 0, 0, 1, 3, 8, TestMD.get(0, 9)));217 }218 219 // FeedbackTexture2D<SAMPLER_FEEDBACK_MIN_MIP> feedbackMinMip;220 {221 ResourceTypeInfo RTI(llvm::TargetExtType::get(222 Context, "dx.FeedbackTexture", {},223 {llvm::to_underlying(SamplerFeedbackType::MinMip),224 llvm::to_underlying(ResourceKind::FeedbackTexture2D)}));225 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::UAV);226 ASSERT_EQ(RTI.isFeedback(), true);227 EXPECT_EQ(RTI.getFeedbackType(), SamplerFeedbackType::MinMip);228 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::FeedbackTexture2D);229 230 ResourceInfo RI(231 /*RecordID=*/0, /*Space=*/0, /*LowerBound=*/0, /*Size=*/1,232 RTI.getHandleTy(), "feedbackMinMip");233 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());234 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x00001011U, 0U);235 EXPECT_MDEQ(RI.getAsMetadata(M, RTI),236 TestMD.get(0, GV, "feedbackMinMip", 0, 0, 1, 17, false, false,237 false, TestMD.get(2, 0)));238 }239 240 // FeedbackTexture2DArray<SAMPLER_FEEDBACK_MIP_REGION_USED> feedbackMipRegion;241 {242 ResourceTypeInfo RTI(llvm::TargetExtType::get(243 Context, "dx.FeedbackTexture", {},244 {llvm::to_underlying(SamplerFeedbackType::MipRegionUsed),245 llvm::to_underlying(ResourceKind::FeedbackTexture2DArray)}));246 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::UAV);247 ASSERT_EQ(RTI.isFeedback(), true);248 EXPECT_EQ(RTI.getFeedbackType(), SamplerFeedbackType::MipRegionUsed);249 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::FeedbackTexture2DArray);250 251 ResourceInfo RI(252 /*RecordID=*/0, /*Space=*/0, /*LowerBound=*/0, /*Size=*/1,253 RTI.getHandleTy(), "feedbackMipRegion");254 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());255 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x00001012U, 0x00000001U);256 EXPECT_MDEQ(RI.getAsMetadata(M, RTI),257 TestMD.get(0, GV, "feedbackMipRegion", 0, 0, 1, 18, false,258 false, false, TestMD.get(2, 1)));259 }260 261 // globallycoherent RWTexture2D<int2> OutputTexture : register(u0, space2);262 {263 ResourceTypeInfo RTI(llvm::TargetExtType::get(264 Context, "dx.Texture", Int32x2Ty,265 {/*IsWriteable=*/1,266 /*IsROV=*/0, /*IsSigned=*/1,267 llvm::to_underlying(ResourceKind::Texture2D)}));268 269 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::UAV);270 EXPECT_EQ(RTI.getUAV().IsROV, false);271 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::Texture2D);272 273 ResourceInfo RI(274 /*RecordID=*/0, /*Space=*/2, /*LowerBound=*/0, /*Size=*/1,275 RTI.getHandleTy(), "OutputTexture");276 RI.GloballyCoherent = true;277 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());278 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x00005002U, 0x00000204U);279 EXPECT_MDEQ(RI.getAsMetadata(M, RTI),280 TestMD.get(0, GV, "OutputTexture", 2, 0, 1, 2, true, false,281 false, TestMD.get(0, 4)));282 283 EXPECT_EQ(RI.GloballyCoherent, true);284 EXPECT_EQ(RI.hasCounter(), false);285 EXPECT_EQ(RI.CounterDirection, ResourceCounterDirection::Unknown);286 }287 288 // RasterizerOrderedBuffer<float4> ROB;289 {290 ResourceTypeInfo RTI(llvm::TargetExtType::get(291 Context, "dx.TypedBuffer", Floatx4Ty,292 {/*IsWriteable=*/1, /*IsROV=*/1, /*IsSigned=*/0}));293 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::UAV);294 EXPECT_EQ(RTI.getUAV().IsROV, true);295 ASSERT_EQ(RTI.isTyped(), true);296 EXPECT_EQ(RTI.getTyped().ElementTy, ElementType::F32);297 EXPECT_EQ(RTI.getTyped().ElementCount, 4u);298 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::TypedBuffer);299 300 ResourceInfo RI(301 /*RecordID=*/0, /*Space=*/0, /*LowerBound=*/0, /*Size=*/1,302 RTI.getHandleTy(), "ROB");303 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());304 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x0000300aU, 0x00000409U);305 EXPECT_MDEQ(RI.getAsMetadata(M, RTI),306 TestMD.get(0, GV, "ROB", 0, 0, 1, 10, false, false, true,307 TestMD.get(0, 9)));308 EXPECT_EQ(RI.GloballyCoherent, false);309 EXPECT_EQ(RI.hasCounter(), false);310 EXPECT_EQ(RI.CounterDirection, ResourceCounterDirection::Unknown);311 }312 313 // RWStructuredBuffer<ParticleMotion> g_OutputBuffer : register(u2);314 {315 StructType *BufType1 = StructType::create(316 Context, {Floatx3Ty, FloatTy, Int32Ty}, "ParticleMotion");317 ResourceTypeInfo RTI(llvm::TargetExtType::get(318 Context, "dx.RawBuffer", BufType1, {/*IsWriteable=*/1, /*IsROV=*/0}));319 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::UAV);320 EXPECT_EQ(RTI.getUAV().IsROV, false);321 ASSERT_EQ(RTI.isStruct(), true);322 EXPECT_EQ(RTI.getStruct(DL).Stride, 20u);323 EXPECT_EQ(RTI.getStruct(DL).AlignLog2, Log2(Align(4)));324 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::StructuredBuffer);325 326 ResourceInfo RI(327 /*RecordID=*/0, /*Space=*/0, /*LowerBound=*/2, /*Size=*/1,328 RTI.getHandleTy(), "g_OutputBuffer");329 RI.CounterDirection = ResourceCounterDirection::Increment;330 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());331 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x0000920cU, 0x00000014U);332 EXPECT_MDEQ(RI.getAsMetadata(M, RTI),333 TestMD.get(0, GV, "g_OutputBuffer", 0, 2, 1, 12, false, true,334 false, TestMD.get(1, 20)));335 EXPECT_EQ(RI.GloballyCoherent, false);336 EXPECT_EQ(RI.hasCounter(), true);337 EXPECT_EQ(RI.CounterDirection, ResourceCounterDirection::Increment);338 }339 340 // RWTexture2DMSArray<uint, 8> g_rw_t2dmsa;341 {342 ResourceTypeInfo RTI(llvm::TargetExtType::get(343 Context, "dx.MSTexture", Int32Ty,344 {/*IsWriteable=*/1, /*SampleCount=*/8, /*IsSigned=*/0,345 llvm::to_underlying(ResourceKind::Texture2DMSArray)}));346 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::UAV);347 EXPECT_EQ(RTI.getUAV().IsROV, false);348 ASSERT_EQ(RTI.isTyped(), true);349 EXPECT_EQ(RTI.getTyped().ElementTy, ElementType::U32);350 EXPECT_EQ(RTI.getTyped().ElementCount, 1u);351 ASSERT_EQ(RTI.isMultiSample(), true);352 EXPECT_EQ(RTI.getMultiSampleCount(), 8u);353 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::Texture2DMSArray);354 355 ResourceInfo RI(356 /*RecordID=*/0, /*Space=*/0, /*LowerBound=*/0, /*Size=*/1,357 RTI.getHandleTy(), "g_rw_t2dmsa");358 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());359 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x00001008U, 0x00080105U);360 EXPECT_MDEQ(RI.getAsMetadata(M, RTI),361 TestMD.get(0, GV, "g_rw_t2dmsa", 0, 0, 1, 8, false, false,362 false, TestMD.get(0, 5)));363 EXPECT_EQ(RI.GloballyCoherent, false);364 EXPECT_EQ(RI.hasCounter(), false);365 EXPECT_EQ(RI.CounterDirection, ResourceCounterDirection::Unknown);366 }367 368 // cbuffer cb0 { float4 g_X; float4 g_Y; }369 {370 StructType *CBufStruct =371 StructType::create(Context, {Floatx4Ty, Floatx4Ty}, "cb0");372 ResourceTypeInfo RTI(373 llvm::TargetExtType::get(Context, "dx.CBuffer", CBufStruct));374 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::CBuffer);375 EXPECT_EQ(RTI.getCBufferSize(DL), 32u);376 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::CBuffer);377 378 ResourceInfo RI(379 /*RecordID=*/0, /*Space=*/0, /*LowerBound=*/0, /*Size=*/1,380 RTI.getHandleTy(), "");381 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());382 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x0000000dU, 0x00000020U);383 EXPECT_MDEQ(RI.getAsMetadata(M, RTI),384 TestMD.get(0, GV, "", 0, 0, 1, 32, nullptr));385 }386 387 // SamplerState ColorMapSampler : register(s0);388 {389 ResourceTypeInfo RTI(llvm::TargetExtType::get(390 Context, "dx.Sampler", {},391 {llvm::to_underlying(dxil::SamplerType::Default)}));392 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::Sampler);393 EXPECT_EQ(RTI.getSamplerType(), dxil::SamplerType::Default);394 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::Sampler);395 396 ResourceInfo RI(397 /*RecordID=*/0, /*Space=*/0, /*LowerBound=*/0, /*Size=*/1,398 RTI.getHandleTy(), "ColorMapSampler");399 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());400 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x0000000eU, 0U);401 EXPECT_MDEQ(RI.getAsMetadata(M, RTI),402 TestMD.get(0, GV, "ColorMapSampler", 0, 0, 1, 0, nullptr));403 }404 405 {406 ResourceTypeInfo RTI(llvm::TargetExtType::get(407 Context, "dx.Sampler", {},408 {llvm::to_underlying(dxil::SamplerType::Comparison)}));409 EXPECT_EQ(RTI.getResourceClass(), ResourceClass::Sampler);410 EXPECT_EQ(RTI.getSamplerType(), dxil::SamplerType::Comparison);411 EXPECT_EQ(RTI.getResourceKind(), ResourceKind::Sampler);412 413 ResourceInfo RI(414 /*RecordID=*/0, /*Space=*/0, /*LowerBound=*/0, /*Size=*/1,415 RTI.getHandleTy(), "CmpSampler");416 GlobalVariable *GV = RI.createSymbol(M, RTI.createElementStruct());417 EXPECT_PROPS_EQ(RI.getAnnotateProps(M, RTI), 0x0000800eU, 0U);418 EXPECT_MDEQ(RI.getAsMetadata(M, RTI),419 TestMD.get(0, GV, "CmpSampler", 0, 0, 1, 1, nullptr));420 }421}422