1//===- AMDGPUMCResourceInfo.cpp --- MC Resource Info ----------------------===//
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-exception
6//
7//===----------------------------------------------------------------------===//
8//
9/// \file
10/// \brief MC infrastructure to propagate the function level resource usage
11/// info.
12///
13//===----------------------------------------------------------------------===//
14
15#include "AMDGPUMCResourceInfo.h"
16#include "SIMachineFunctionInfo.h"
17#include "Utils/AMDGPUBaseInfo.h"
18#include "llvm/ADT/StringRef.h"
19#include "llvm/MC/MCAsmInfo.h"
20#include "llvm/MC/MCContext.h"
21#include "llvm/MC/MCSymbol.h"
22#include "llvm/Target/TargetMachine.h"
23
24#define DEBUG_TYPE "amdgpu-mc-resource-usage"
25
26using namespace llvm;
27
28MCSymbol *MCResourceInfo::getSymbol(StringRef FuncName, ResourceInfoKind RIK,
29 MCContext &OutContext) {
30 auto GOCS = [FuncName, &OutContext](StringRef Suffix) {
31 StringRef Prefix = OutContext.getAsmInfo().getInternalSymbolPrefix();
32 return OutContext.getOrCreateSymbol(Name: Twine(Prefix) + FuncName +
33 Twine(Suffix));
34 };
35 switch (RIK) {
36 case RIK_NumVGPR:
37 return GOCS(".num_vgpr");
38 case RIK_NumAGPR:
39 return GOCS(".num_agpr");
40 case RIK_NumSGPR:
41 return GOCS(".numbered_sgpr");
42 case RIK_NumNamedBarrier:
43 return GOCS(".num_named_barrier");
44 case RIK_PrivateSegSize:
45 return GOCS(".private_seg_size");
46 case RIK_UsesVCC:
47 return GOCS(".uses_vcc");
48 case RIK_UsesFlatScratch:
49 return GOCS(".uses_flat_scratch");
50 case RIK_HasDynSizedStack:
51 return GOCS(".has_dyn_sized_stack");
52 case RIK_HasRecursion:
53 return GOCS(".has_recursion");
54 case RIK_HasIndirectCall:
55 return GOCS(".has_indirect_call");
56 }
57 llvm_unreachable("Unexpected ResourceInfoKind.");
58}
59
60const MCExpr *MCResourceInfo::getSymRefExpr(StringRef FuncName,
61 ResourceInfoKind RIK,
62 MCContext &Ctx) {
63 return MCSymbolRefExpr::create(Symbol: getSymbol(FuncName, RIK, OutContext&: Ctx), Ctx);
64}
65
66void MCResourceInfo::assignMaxRegs(MCContext &OutContext) {
67 // Assign expression to get the max register use to the max_num_Xgpr symbol.
68 MCSymbol *MaxVGPRSym = getMaxVGPRSymbol(OutContext);
69 MCSymbol *MaxAGPRSym = getMaxAGPRSymbol(OutContext);
70 MCSymbol *MaxSGPRSym = getMaxSGPRSymbol(OutContext);
71 MCSymbol *MaxNamedBarrierSym = getMaxNamedBarrierSymbol(OutContext);
72
73 auto assignMaxRegSym = [&OutContext](MCSymbol *Sym, int32_t RegCount) {
74 const MCExpr *MaxExpr = MCConstantExpr::create(Value: RegCount, Ctx&: OutContext);
75 Sym->setVariableValue(MaxExpr);
76 };
77
78 assignMaxRegSym(MaxVGPRSym, MaxVGPR);
79 assignMaxRegSym(MaxAGPRSym, MaxAGPR);
80 assignMaxRegSym(MaxSGPRSym, MaxSGPR);
81 assignMaxRegSym(MaxNamedBarrierSym, MaxNamedBarrier);
82}
83
84void MCResourceInfo::reset() { *this = MCResourceInfo(); }
85
86void MCResourceInfo::finalize(MCContext &OutContext) {
87 assert(!Finalized && "Cannot finalize ResourceInfo again.");
88 Finalized = true;
89 assignMaxRegs(OutContext);
90}
91
92MCSymbol *MCResourceInfo::getMaxVGPRSymbol(MCContext &OutContext) {
93 return OutContext.getOrCreateSymbol(Name: "amdgpu.max_num_vgpr");
94}
95
96MCSymbol *MCResourceInfo::getMaxAGPRSymbol(MCContext &OutContext) {
97 return OutContext.getOrCreateSymbol(Name: "amdgpu.max_num_agpr");
98}
99
100MCSymbol *MCResourceInfo::getMaxSGPRSymbol(MCContext &OutContext) {
101 return OutContext.getOrCreateSymbol(Name: "amdgpu.max_num_sgpr");
102}
103
104MCSymbol *MCResourceInfo::getMaxNamedBarrierSymbol(MCContext &OutContext) {
105 return OutContext.getOrCreateSymbol(Name: "amdgpu.max_num_named_barrier");
106}
107
108// Tries to flatten recursive call register resource gathering. Simple cycle
109// avoiding dfs to find the constants in the propagated symbols.
110// Assumes:
111// - RecSym has been confirmed to recurse (this means the callee symbols should
112// all be populated, started at RecSym).
113// - Shape of the resource symbol's MCExpr (`max` args are order agnostic):
114// RecSym.MCExpr := max(<constant>+, <callee_symbol>*)
115const MCExpr *MCResourceInfo::flattenedCycleMax(MCSymbol *RecSym,
116 ResourceInfoKind RIK,
117 MCContext &OutContext) {
118 SmallPtrSet<const MCExpr *, 8> Seen;
119 SmallVector<const MCExpr *, 8> WorkList;
120 int64_t Maximum = 0;
121
122 const MCExpr *RecExpr = RecSym->getVariableValue();
123 WorkList.push_back(Elt: RecExpr);
124
125 while (!WorkList.empty()) {
126 const MCExpr *CurExpr = WorkList.pop_back_val();
127 switch (CurExpr->getKind()) {
128 default: {
129 // Assuming the recursion is of shape `max(<constant>, <callee_symbol>)`
130 // where <callee_symbol> will eventually recurse. If this condition holds,
131 // the recursion occurs within some other (possibly unresolvable) MCExpr,
132 // thus using the worst case value then.
133 if (!AMDGPUMCExpr::isSymbolUsedInExpression(Sym: RecSym, E: CurExpr)) {
134 LLVM_DEBUG(dbgs() << "MCResUse: " << RecSym->getName()
135 << ": Recursion in unexpected sub-expression, using "
136 "module maximum\n");
137 switch (RIK) {
138 default:
139 break;
140 case RIK_NumVGPR:
141 return MCSymbolRefExpr::create(Symbol: getMaxVGPRSymbol(OutContext),
142 Ctx&: OutContext);
143 break;
144 case RIK_NumSGPR:
145 return MCSymbolRefExpr::create(Symbol: getMaxSGPRSymbol(OutContext),
146 Ctx&: OutContext);
147 break;
148 case RIK_NumAGPR:
149 return MCSymbolRefExpr::create(Symbol: getMaxAGPRSymbol(OutContext),
150 Ctx&: OutContext);
151 break;
152 }
153 }
154 break;
155 }
156 case MCExpr::ExprKind::Constant: {
157 int64_t Val = cast<MCConstantExpr>(Val: CurExpr)->getValue();
158 Maximum = std::max(a: Maximum, b: Val);
159 break;
160 }
161 case MCExpr::ExprKind::SymbolRef: {
162 const MCSymbolRefExpr *SymExpr = cast<MCSymbolRefExpr>(Val: CurExpr);
163 const MCSymbol &SymRef = SymExpr->getSymbol();
164 if (SymRef.isVariable()) {
165 const MCExpr *SymVal = SymRef.getVariableValue();
166 if (Seen.insert(Ptr: SymVal).second)
167 WorkList.push_back(Elt: SymVal);
168 }
169 break;
170 }
171 case MCExpr::ExprKind::Target: {
172 const AMDGPUMCExpr *TargetExpr = cast<AMDGPUMCExpr>(Val: CurExpr);
173 if (TargetExpr->getKind() == AMDGPUMCExpr::VariantKind::AGVK_Max) {
174 for (auto &Arg : TargetExpr->getArgs())
175 WorkList.push_back(Elt: Arg);
176 }
177 break;
178 }
179 }
180 }
181
182 LLVM_DEBUG(dbgs() << "MCResUse: " << RecSym->getName()
183 << ": Using flattened max: << " << Maximum << '\n');
184
185 return MCConstantExpr::create(Value: Maximum, Ctx&: OutContext);
186}
187
188void MCResourceInfo::assignResourceInfoExpr(
189 int64_t LocalValue, ResourceInfoKind RIK, AMDGPUMCExpr::VariantKind Kind,
190 const MachineFunction &MF, const SmallVectorImpl<const Function *> &Callees,
191 MCContext &OutContext) {
192 const TargetMachine &TM = MF.getTarget();
193 MCSymbol *FnSym = TM.getSymbol(GV: &MF.getFunction());
194 const MCConstantExpr *LocalConstExpr =
195 MCConstantExpr::create(Value: LocalValue, Ctx&: OutContext);
196 const MCExpr *SymVal = LocalConstExpr;
197 MCSymbol *Sym = getSymbol(FuncName: FnSym->getName(), RIK, OutContext);
198 LLVM_DEBUG(dbgs() << "MCResUse: " << Sym->getName() << ": Adding "
199 << LocalValue << " as function local usage\n");
200 if (!Callees.empty()) {
201 SmallVector<const MCExpr *, 8> ArgExprs;
202 SmallPtrSet<const Function *, 8> Seen;
203 ArgExprs.push_back(Elt: LocalConstExpr);
204
205 for (const Function *Callee : Callees) {
206 if (!Seen.insert(Ptr: Callee).second)
207 continue;
208
209 MCSymbol *CalleeFnSym = TM.getSymbol(GV: &Callee->getFunction());
210 MCSymbol *CalleeValSym =
211 getSymbol(FuncName: CalleeFnSym->getName(), RIK, OutContext);
212
213 // Avoid constructing recursive definitions by detecting whether `Sym` is
214 // found transitively within any of its `CalleeValSym`.
215 if (!CalleeValSym->isVariable() ||
216 !AMDGPUMCExpr::isSymbolUsedInExpression(
217 Sym, E: CalleeValSym->getVariableValue())) {
218 LLVM_DEBUG(dbgs() << "MCResUse: " << Sym->getName() << ": Adding "
219 << CalleeValSym->getName() << " as callee\n");
220 ArgExprs.push_back(Elt: MCSymbolRefExpr::create(Symbol: CalleeValSym, Ctx&: OutContext));
221 } else {
222 LLVM_DEBUG(dbgs() << "MCResUse: " << Sym->getName()
223 << ": Recursion found, attempt flattening of cycle "
224 "for resource usage\n");
225 // In case of recursion for vgpr/sgpr/agpr resource usage: try to
226 // flatten and use the max of the call cycle. May still end up emitting
227 // module max if not fully resolvable.
228 switch (RIK) {
229 default:
230 break;
231 case RIK_NumVGPR:
232 case RIK_NumSGPR:
233 case RIK_NumAGPR:
234 ArgExprs.push_back(Elt: flattenedCycleMax(RecSym: CalleeValSym, RIK, OutContext));
235 break;
236 case RIK_NumNamedBarrier:
237 ArgExprs.push_back(Elt: MCSymbolRefExpr::create(
238 Symbol: getMaxNamedBarrierSymbol(OutContext), Ctx&: OutContext));
239 break;
240 }
241 }
242 }
243 if (ArgExprs.size() > 1)
244 SymVal = AMDGPUMCExpr::create(Kind, Args: ArgExprs, Ctx&: OutContext);
245 }
246 Sym->setVariableValue(SymVal);
247}
248
249void MCResourceInfo::gatherResourceInfo(
250 const MachineFunction &MF,
251 const AMDGPUResourceUsageAnalysisWrapperPass::FunctionResourceInfo &FRI,
252 MCContext &OutContext) {
253 // Worst case VGPR use for non-hardware-entrypoints.
254 MCSymbol *MaxVGPRSym = getMaxVGPRSymbol(OutContext);
255 MCSymbol *MaxAGPRSym = getMaxAGPRSymbol(OutContext);
256 MCSymbol *MaxSGPRSym = getMaxSGPRSymbol(OutContext);
257 MCSymbol *MaxNamedBarrierSym = getMaxNamedBarrierSymbol(OutContext);
258
259 CallingConv::ID CC = MF.getFunction().getCallingConv();
260 bool IsChainCC = AMDGPU::isChainCC(CC);
261 bool IsDynamicVGPREnabled =
262 MF.getInfo<SIMachineFunctionInfo>()->isDynamicVGPREnabled();
263
264 if (!AMDGPU::isEntryFunctionCC(CC)) {
265 if (!IsDynamicVGPREnabled || !IsChainCC)
266 addMaxVGPRCandidate(candidate: FRI.NumVGPR);
267 addMaxAGPRCandidate(candidate: FRI.NumAGPR);
268 addMaxSGPRCandidate(candidate: FRI.NumExplicitSGPR);
269 addMaxNamedBarrierCandidate(candidate: FRI.NumNamedBarrier);
270 }
271
272 const TargetMachine &TM = MF.getTarget();
273 MCSymbol *FnSym = TM.getSymbol(GV: &MF.getFunction());
274
275 LLVM_DEBUG(dbgs() << "MCResUse: Gathering resource information for "
276 << FnSym->getName() << '\n');
277
278 auto SetToLocal = [&](int64_t Value, ResourceInfoKind RIK) {
279 MCSymbol *Sym = getSymbol(FuncName: FnSym->getName(), RIK, OutContext);
280 Sym->setVariableValue(MCConstantExpr::create(Value, Ctx&: OutContext));
281 };
282
283 // When link-time object linking is enabled, set all resource symbols to
284 // concrete local values.
285 if (AMDGPUTargetMachine::EnableObjectLinking) {
286 LLVM_DEBUG(dbgs() << "MCResUse: object linking enabled, no call-graph "
287 "propagation; emitting local resource values only\n");
288 SetToLocal(FRI.NumVGPR, RIK_NumVGPR);
289 SetToLocal(FRI.NumAGPR, RIK_NumAGPR);
290 SetToLocal(FRI.NumExplicitSGPR, RIK_NumSGPR);
291 SetToLocal(FRI.NumNamedBarrier, RIK_NumNamedBarrier);
292 SetToLocal(FRI.PrivateSegmentSize, RIK_PrivateSegSize);
293 SetToLocal(FRI.UsesVCC, RIK_UsesVCC);
294 SetToLocal(FRI.UsesFlatScratch, RIK_UsesFlatScratch);
295 SetToLocal(FRI.HasDynamicallySizedStack, RIK_HasDynSizedStack);
296 SetToLocal(FRI.HasRecursion, RIK_HasRecursion);
297 SetToLocal(FRI.HasIndirectCall, RIK_HasIndirectCall);
298 return;
299 }
300
301 LLVM_DEBUG({
302 if (!FRI.Callees.empty()) {
303 dbgs() << "MCResUse: Callees:\n";
304 for (const Function *Callee : FRI.Callees) {
305 MCSymbol *CalleeFnSym = TM.getSymbol(&Callee->getFunction());
306 dbgs() << "MCResUse: " << CalleeFnSym->getName() << '\n';
307 }
308 }
309 });
310
311 auto SetMaxReg = [&](MCSymbol *MaxSym, int32_t numRegs,
312 ResourceInfoKind RIK) {
313 if (!FRI.HasIndirectCall) {
314 assignResourceInfoExpr(LocalValue: numRegs, RIK, Kind: AMDGPUMCExpr::AGVK_Max, MF,
315 Callees: FRI.Callees, OutContext);
316 } else {
317 const MCExpr *SymRef = MCSymbolRefExpr::create(Symbol: MaxSym, Ctx&: OutContext);
318 MCSymbol *LocalNumSym = getSymbol(FuncName: FnSym->getName(), RIK, OutContext);
319 const MCExpr *MaxWithLocal = AMDGPUMCExpr::createMax(
320 Args: {MCConstantExpr::create(Value: numRegs, Ctx&: OutContext), SymRef}, Ctx&: OutContext);
321 LocalNumSym->setVariableValue(MaxWithLocal);
322 LLVM_DEBUG(dbgs() << "MCResUse: " << LocalNumSym->getName()
323 << ": Indirect callee within, using module maximum\n");
324 }
325 };
326
327 LLVM_DEBUG(dbgs() << "MCResUse: " << FnSym->getName() << '\n');
328
329 // When DynamicVGPR is enabled, chain functions should not propagate VGPR
330 // counts from other chain callees since each chain function can have its own
331 // VGPR allocation, but should still propagate from non-chain callees.
332 if (IsDynamicVGPREnabled && (IsChainCC || CC == CallingConv::AMDGPU_CS)) {
333 if (FRI.HasNonChainIndirectCall) {
334 // Has indirect calls to non-chain functions. Use max of local count and
335 // module-wide non-chain maximum.
336 MCSymbol *Sym = getSymbol(FuncName: FnSym->getName(), RIK: RIK_NumVGPR, OutContext);
337 Sym->setVariableValue(AMDGPUMCExpr::createMax(
338 Args: {MCConstantExpr::create(Value: FRI.NumVGPR, Ctx&: OutContext),
339 MCSymbolRefExpr::create(Symbol: MaxVGPRSym, Ctx&: OutContext)},
340 Ctx&: OutContext));
341 SetMaxReg(MaxAGPRSym, FRI.NumAGPR, RIK_NumAGPR);
342 } else {
343 // No indirect calls to non-chain functions. Propagate from direct callees
344 assignResourceInfoExpr(LocalValue: FRI.NumVGPR, RIK: RIK_NumVGPR, Kind: AMDGPUMCExpr::AGVK_Max,
345 MF, Callees: FRI.Callees, OutContext);
346 assignResourceInfoExpr(LocalValue: FRI.NumAGPR, RIK: RIK_NumAGPR, Kind: AMDGPUMCExpr::AGVK_Max,
347 MF, Callees: FRI.Callees, OutContext);
348 }
349 } else {
350 SetMaxReg(MaxVGPRSym, FRI.NumVGPR, RIK_NumVGPR);
351 SetMaxReg(MaxAGPRSym, FRI.NumAGPR, RIK_NumAGPR);
352 }
353 SetMaxReg(MaxSGPRSym, FRI.NumExplicitSGPR, RIK_NumSGPR);
354 SetMaxReg(MaxNamedBarrierSym, FRI.NumNamedBarrier, RIK_NumNamedBarrier);
355
356 {
357 // The expression for private segment size should be: FRI.PrivateSegmentSize
358 // + max(FRI.Callees, FRI.CalleeSegmentSize)
359 SmallVector<const MCExpr *, 8> ArgExprs;
360 MCSymbol *Sym = getSymbol(FuncName: FnSym->getName(), RIK: RIK_PrivateSegSize, OutContext);
361 if (FRI.CalleeSegmentSize) {
362 LLVM_DEBUG(dbgs() << "MCResUse: " << Sym->getName() << ": Adding "
363 << FRI.CalleeSegmentSize
364 << " for indirect/recursive callees within\n");
365 ArgExprs.push_back(
366 Elt: MCConstantExpr::create(Value: FRI.CalleeSegmentSize, Ctx&: OutContext));
367 }
368
369 SmallPtrSet<const Function *, 8> Seen;
370 Seen.insert(Ptr: &MF.getFunction());
371 for (const Function *Callee : FRI.Callees) {
372 if (!Seen.insert(Ptr: Callee).second)
373 continue;
374 if (!Callee->isDeclaration()) {
375 MCSymbol *CalleeFnSym = TM.getSymbol(GV: &Callee->getFunction());
376 MCSymbol *CalleeValSym =
377 getSymbol(FuncName: CalleeFnSym->getName(), RIK: RIK_PrivateSegSize, OutContext);
378
379 // Avoid constructing recursive definitions by detecting whether `Sym`
380 // is found transitively within any of its `CalleeValSym`.
381 if (!CalleeValSym->isVariable() ||
382 !AMDGPUMCExpr::isSymbolUsedInExpression(
383 Sym, E: CalleeValSym->getVariableValue())) {
384 LLVM_DEBUG(dbgs() << "MCResUse: " << Sym->getName() << ": Adding "
385 << CalleeValSym->getName() << " as callee\n");
386 ArgExprs.push_back(Elt: MCSymbolRefExpr::create(Symbol: CalleeValSym, Ctx&: OutContext));
387 }
388 }
389 }
390 const MCExpr *localConstExpr =
391 MCConstantExpr::create(Value: FRI.PrivateSegmentSize, Ctx&: OutContext);
392 LLVM_DEBUG(dbgs() << "MCResUse: " << Sym->getName() << ": Adding "
393 << FRI.PrivateSegmentSize
394 << " as function local usage\n");
395 if (!ArgExprs.empty()) {
396 const AMDGPUMCExpr *transitiveExpr =
397 AMDGPUMCExpr::createMax(Args: ArgExprs, Ctx&: OutContext);
398 localConstExpr =
399 MCBinaryExpr::createAdd(LHS: localConstExpr, RHS: transitiveExpr, Ctx&: OutContext);
400 }
401 Sym->setVariableValue(localConstExpr);
402 }
403
404 if (!FRI.HasIndirectCall) {
405 assignResourceInfoExpr(LocalValue: FRI.UsesVCC, RIK: ResourceInfoKind::RIK_UsesVCC,
406 Kind: AMDGPUMCExpr::AGVK_Or, MF, Callees: FRI.Callees, OutContext);
407 assignResourceInfoExpr(LocalValue: FRI.UsesFlatScratch,
408 RIK: ResourceInfoKind::RIK_UsesFlatScratch,
409 Kind: AMDGPUMCExpr::AGVK_Or, MF, Callees: FRI.Callees, OutContext);
410 assignResourceInfoExpr(LocalValue: FRI.HasDynamicallySizedStack,
411 RIK: ResourceInfoKind::RIK_HasDynSizedStack,
412 Kind: AMDGPUMCExpr::AGVK_Or, MF, Callees: FRI.Callees, OutContext);
413 assignResourceInfoExpr(LocalValue: FRI.HasRecursion, RIK: ResourceInfoKind::RIK_HasRecursion,
414 Kind: AMDGPUMCExpr::AGVK_Or, MF, Callees: FRI.Callees, OutContext);
415 assignResourceInfoExpr(LocalValue: FRI.HasIndirectCall,
416 RIK: ResourceInfoKind::RIK_HasIndirectCall,
417 Kind: AMDGPUMCExpr::AGVK_Or, MF, Callees: FRI.Callees, OutContext);
418 } else {
419 SetToLocal(FRI.UsesVCC, ResourceInfoKind::RIK_UsesVCC);
420 SetToLocal(FRI.UsesFlatScratch, ResourceInfoKind::RIK_UsesFlatScratch);
421 SetToLocal(FRI.HasDynamicallySizedStack,
422 ResourceInfoKind::RIK_HasDynSizedStack);
423 SetToLocal(FRI.HasRecursion, ResourceInfoKind::RIK_HasRecursion);
424 SetToLocal(FRI.HasIndirectCall, ResourceInfoKind::RIK_HasIndirectCall);
425 }
426}
427
428const MCExpr *MCResourceInfo::createTotalNumVGPRs(const MachineFunction &MF,
429 MCContext &Ctx) {
430 const TargetMachine &TM = MF.getTarget();
431 MCSymbol *FnSym = TM.getSymbol(GV: &MF.getFunction());
432 return AMDGPUMCExpr::createTotalNumVGPR(
433 NumAGPR: getSymRefExpr(FuncName: FnSym->getName(), RIK: RIK_NumAGPR, Ctx),
434 NumVGPR: getSymRefExpr(FuncName: FnSym->getName(), RIK: RIK_NumVGPR, Ctx), Ctx);
435}
436
437const MCExpr *MCResourceInfo::createTotalNumSGPRs(const MachineFunction &MF,
438 bool hasXnack,
439 MCContext &Ctx) {
440 const TargetMachine &TM = MF.getTarget();
441 MCSymbol *FnSym = TM.getSymbol(GV: &MF.getFunction());
442 return MCBinaryExpr::createAdd(
443 LHS: getSymRefExpr(FuncName: FnSym->getName(), RIK: RIK_NumSGPR, Ctx),
444 RHS: AMDGPUMCExpr::createExtraSGPRs(
445 VCCUsed: getSymRefExpr(FuncName: FnSym->getName(), RIK: RIK_UsesVCC, Ctx),
446 FlatScrUsed: getSymRefExpr(FuncName: FnSym->getName(), RIK: RIK_UsesFlatScratch, Ctx), XNACKUsed: hasXnack,
447 Ctx),
448 Ctx);
449}
450