Skip to content

Commit 51df874

Browse files
committed
[Distributed] IRGen: Support loading both concrete and generic argument values
To load generic argument value, we need to retrieve argument type from the argument types buffer, based on that load value size and alignment, and only then fetch actual value from the argument buffer as `swift.opaque *`.
1 parent e7ceddc commit 51df874

File tree

1 file changed

+203
-66
lines changed

1 file changed

+203
-66
lines changed

lib/IRGen/GenDistributed.cpp

Lines changed: 203 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "GenCall.h"
2525
#include "GenDecl.h"
2626
#include "GenMeta.h"
27+
#include "GenOpaque.h"
2728
#include "GenProto.h"
2829
#include "GenType.h"
2930
#include "IRGenDebugInfo.h"
@@ -66,8 +67,7 @@ llvm::Value *irgen::emitDistributedActorInitializeRemote(
6667
namespace {
6768

6869
struct AllocationInfo {
69-
SILType Type;
70-
const TypeInfo &TI;
70+
unsigned ArgumentIdx;
7171
StackAddress Addr;
7272
};
7373

@@ -97,6 +97,30 @@ class DistributedAccessor {
9797
llvm::Value *argumentTypes,
9898
Explosion &arguments);
9999

100+
/// Load an argument value from the given buffer \c argumentSlot
101+
/// to the given explosion \c arguments. Type of the argument is
102+
/// the same as parameter type.
103+
///
104+
/// Returns a pair of aligned offset and value size.
105+
std::pair<llvm::Value *, llvm::Value *>
106+
loadArgument(unsigned argumentIdx,
107+
llvm::Value *argumentSlot,
108+
SILType paramType,
109+
ParameterConvention convention,
110+
Explosion &arguments);
111+
112+
/// Load an argument value from the given buffer \c argumentSlot
113+
/// to the given explosion \c arguments. Information describing
114+
/// the type of argument comes from runtime metadata.
115+
///
116+
/// Returns a pair of aligned offset and value size.
117+
std::pair<llvm::Value *, llvm::Value *>
118+
loadArgument(unsigned argumentIdx,
119+
llvm::Value *argumentSlot,
120+
llvm::Value *argumentType,
121+
ParameterConvention convention,
122+
Explosion &arguments);
123+
100124
FunctionPointer getPointerToTarget() const;
101125

102126
Callee getCalleeForDistributedTarget(llvm::Value *self) const;
@@ -204,88 +228,191 @@ void DistributedAccessor::computeArguments(llvm::Value *argumentBuffer,
204228
// Initialize "offset" with the address of the base of the argument buffer.
205229
IGF.Builder.CreateStore(argumentBuffer, offset);
206230

207-
for (const auto &param : parameters) {
231+
// Cast type buffer to `swift.type**`
232+
argumentTypes =
233+
IGF.Builder.CreateBitCast(argumentTypes, IGM.TypeMetadataPtrPtrTy);
234+
235+
for (unsigned i = 0, n = parameters.size(); i != n; ++i) {
236+
const auto &param = parameters[i];
208237
auto paramTy = param.getSILStorageInterfaceType();
209-
const TypeInfo &typeInfo = IGF.getTypeInfo(paramTy);
210238

211-
// 1. Load current offset.
212-
llvm::Value *currentOffset = IGF.Builder.CreateLoad(offset, "elt_offset");
239+
// The size of the loaded argument value
240+
llvm::Value *valueSize;
213241

214-
// 2. Cast the pointer to the type of the element.
215-
Address eltPtr = IGF.Builder.CreateBitCast(
216-
Address(currentOffset, IGM.getPointerAlignment()),
217-
IGM.getStoragePointerType(paramTy));
242+
// Load current offset
243+
llvm::Value *currentOffset = IGF.Builder.CreateLoad(offset, "elt_offset");
218244

219-
// 3. Adjust typed pointer to the alignment of the type.
220-
auto alignedOffset = typeInfo.roundUpToTypeAlignment(IGF, eltPtr, paramTy);
245+
// Load argument value into the provided explosion
246+
if (paramTy.hasTypeParameter()) {
247+
auto offset =
248+
Size(i * IGM.DataLayout.getTypeAllocSize(IGM.TypeMetadataPtrTy));
249+
auto alignment =
250+
IGM.DataLayout.getABITypeAlignment(IGM.TypeMetadataPtrTy);
251+
252+
// Load metadata describing argument value from argument types buffer.
253+
auto typeLoc = IGF.emitAddressAtOffset(
254+
argumentTypes, Offset(offset), IGM.TypeMetadataPtrTy,
255+
Alignment(alignment), "arg_type_loc");
256+
257+
auto *argumentTy = IGF.Builder.CreateLoad(typeLoc, "arg_type");
258+
259+
// Load argument value based using loaded type metadata.
260+
std::tie(currentOffset, valueSize) = loadArgument(
261+
i, currentOffset, argumentTy, param.getConvention(), arguments);
262+
} else {
263+
std::tie(currentOffset, valueSize) = loadArgument(
264+
i, currentOffset, paramTy, param.getConvention(), arguments);
265+
}
221266

222-
if (paramTy.isObject()) {
223-
auto &nativeSchema = typeInfo.nativeParameterValueSchema(IGM);
224-
// If schema is empty, skip to the next argument.
225-
if (nativeSchema.empty())
226-
continue;
267+
// Move the offset to the beginning of the next element, unless
268+
// this is the last element
269+
if (param != parameters.back()) {
270+
llvm::Value *addr = IGF.Builder.CreatePtrToInt(currentOffset, IGM.IntPtrTy);
271+
llvm::Value *nextOffset = IGF.Builder.CreateIntToPtr(
272+
IGF.Builder.CreateAdd(addr, valueSize), IGM.Int8PtrTy);
273+
IGF.Builder.CreateStore(nextOffset, offset);
227274
}
275+
}
228276

229-
// 4. Create an exploded version of the type to pass as an
230-
// argument to distributed method.
277+
IGF.Builder.CreateLifetimeEnd(offset, IGM.getPointerSize());
278+
}
231279

232-
switch (param.getConvention()) {
233-
case ParameterConvention::Indirect_In:
234-
case ParameterConvention::Indirect_In_Constant: {
235-
// The +1 argument is passed indirectly, so we need to copy it into
236-
// a temporary.
280+
std::pair<llvm::Value *, llvm::Value *>
281+
DistributedAccessor::loadArgument(unsigned argumentIdx,
282+
llvm::Value *argumentSlot,
283+
llvm::Value *argumentType,
284+
ParameterConvention convention,
285+
Explosion &arguments) {
286+
// TODO: `emitLoad*` would actually load value witness table every
287+
// time it's called, which is sub-optimal but all of the APIs that
288+
// deal with value witness tables are currently hidden in GenOpaque.cpp
289+
llvm::Value *valueSize = emitLoadOfSize(IGF, argumentType);
290+
291+
llvm::Value *isInline, *flags;
292+
std::tie(isInline, flags) = emitLoadOfIsInline(IGF, argumentType);
293+
294+
llvm::Value *alignmentMask = emitAlignMaskFromFlags(IGF, flags);
295+
296+
// Align argument value offset as required by its type metadata.
297+
llvm::Value *alignedSlot =
298+
IGF.Builder.CreatePtrToInt(argumentSlot, IGM.IntPtrTy);
299+
{
300+
alignedSlot = IGF.Builder.CreateNUWAdd(alignedSlot, alignmentMask);
237301

238-
auto stackAddr = typeInfo.allocateStack(IGF, paramTy, "arg.temp");
239-
auto argPtr = stackAddr.getAddress().getAddress();
302+
llvm::Value *invertedMask = IGF.Builder.CreateNot(alignmentMask);
303+
alignedSlot = IGF.Builder.CreateAnd(alignedSlot, invertedMask);
304+
alignedSlot =
305+
IGF.Builder.CreateIntToPtr(alignedSlot, IGM.OpaquePtrTy);
306+
}
240307

241-
typeInfo.initializeWithCopy(IGF, stackAddr.getAddress(), alignedOffset,
242-
paramTy, /*isOutlined=*/false);
243-
arguments.add(argPtr);
308+
switch (convention) {
309+
case ParameterConvention::Indirect_In:
310+
case ParameterConvention::Indirect_In_Constant: {
311+
// The +1 argument is passed indirectly, so we need to copy it into
312+
// a temporary.
244313

245-
// Remember to deallocate later.
246-
AllocatedArguments.push_back({paramTy, typeInfo, stackAddr});
247-
break;
248-
}
314+
auto stackAddr =
315+
IGF.emitDynamicAlloca(IGM.Int8Ty, valueSize, Alignment(16));
316+
auto argPtr = stackAddr.getAddress().getAddress();
249317

250-
case ParameterConvention::Indirect_In_Guaranteed: {
251-
// The argument is +0, so we can use the address of the param in
252-
// the context directly.
253-
arguments.add(alignedOffset.getAddress());
254-
break;
255-
}
318+
emitInitializeWithCopyCall(IGF, argumentType, stackAddr.getAddress(),
319+
Address(alignedSlot, IGM.getPointerAlignment()));
320+
arguments.add(argPtr);
256321

257-
case ParameterConvention::Indirect_Inout:
258-
case ParameterConvention::Indirect_InoutAliasable:
259-
llvm_unreachable("indirect parameters are not supported");
322+
// Remember to deallocate later.
323+
AllocatedArguments.push_back({argumentIdx, stackAddr});
324+
break;
325+
}
260326

261-
case ParameterConvention::Direct_Guaranteed:
262-
case ParameterConvention::Direct_Unowned: {
263-
cast<LoadableTypeInfo>(typeInfo).loadAsTake(IGF, alignedOffset,
264-
arguments);
265-
break;
266-
}
327+
case ParameterConvention::Indirect_In_Guaranteed: {
328+
// The argument is +0, so we can use the address of the param in
329+
// the context directly.
330+
arguments.add(alignedSlot);
331+
break;
332+
}
267333

268-
case ParameterConvention::Direct_Owned:
269-
// Copy the value out at +1.
270-
cast<LoadableTypeInfo>(typeInfo).loadAsCopy(IGF, alignedOffset,
271-
arguments);
272-
}
334+
case ParameterConvention::Indirect_Inout:
335+
case ParameterConvention::Indirect_InoutAliasable:
336+
llvm_unreachable("indirect 'inout' parameters are not supported");
273337

274-
// 6. Move the offset to the beginning of the next element, unless
275-
// this is the last element.
276-
if (param != parameters.back()) {
277-
llvm::Value *typeSize = typeInfo.getSize(IGF, paramTy);
338+
case ParameterConvention::Direct_Guaranteed:
339+
case ParameterConvention::Direct_Unowned:
340+
case ParameterConvention::Direct_Owned:
341+
llvm_unreachable("cannot pass opaque type directly");
342+
}
278343

279-
llvm::Value *addr = alignedOffset.getAddress();
280-
addr = IGF.Builder.CreatePtrToInt(addr, IGM.IntPtrTy);
281-
llvm::Value *nextOffset = IGF.Builder.CreateIntToPtr(
282-
IGF.Builder.CreateAdd(addr, typeSize), IGM.Int8PtrTy);
344+
return {alignedSlot, valueSize};
345+
}
283346

284-
IGF.Builder.CreateStore(nextOffset, offset);
285-
}
347+
std::pair<llvm::Value *, llvm::Value *>
348+
DistributedAccessor::loadArgument(unsigned argumentIdx,
349+
llvm::Value *argumentSlot,
350+
SILType paramTy,
351+
ParameterConvention convention,
352+
Explosion &arguments) {
353+
const TypeInfo &typeInfo = IGF.getTypeInfo(paramTy);
354+
355+
// 1. Check whether the native representation is empty e.g.
356+
// this happens for empty enums.
357+
if (paramTy.isObject()) {
358+
auto &nativeSchema = typeInfo.nativeParameterValueSchema(IGM);
359+
// If schema is empty, skip to the next argument.
360+
if (nativeSchema.empty())
361+
return {argumentSlot, typeInfo.getSize(IGF, paramTy)};
286362
}
287363

288-
IGF.Builder.CreateLifetimeEnd(offset, IGM.getPointerSize());
364+
// 2. Cast the pointer to the type of the element.
365+
Address eltPtr = IGF.Builder.CreateBitCast(
366+
Address(argumentSlot, IGM.getPointerAlignment()),
367+
IGM.getStoragePointerType(paramTy));
368+
369+
// 3. Adjust typed pointer to the alignment of the type.
370+
auto alignedOffset = typeInfo.roundUpToTypeAlignment(IGF, eltPtr, paramTy);
371+
372+
// 4. Create an exploded version of the type to pass as an
373+
// argument to distributed method.
374+
375+
switch (convention) {
376+
case ParameterConvention::Indirect_In:
377+
case ParameterConvention::Indirect_In_Constant: {
378+
// The +1 argument is passed indirectly, so we need to copy it into
379+
// a temporary.
380+
381+
auto stackAddr = typeInfo.allocateStack(IGF, paramTy, "arg.temp");
382+
auto argPtr = stackAddr.getAddress().getAddress();
383+
384+
typeInfo.initializeWithCopy(IGF, stackAddr.getAddress(), alignedOffset,
385+
paramTy, /*isOutlined=*/false);
386+
arguments.add(argPtr);
387+
388+
// Remember to deallocate later.
389+
AllocatedArguments.push_back({argumentIdx, stackAddr});
390+
break;
391+
}
392+
393+
case ParameterConvention::Indirect_In_Guaranteed: {
394+
// The argument is +0, so we can use the address of the param in
395+
// the context directly.
396+
arguments.add(alignedOffset.getAddress());
397+
break;
398+
}
399+
400+
case ParameterConvention::Indirect_Inout:
401+
case ParameterConvention::Indirect_InoutAliasable:
402+
llvm_unreachable("indirect 'inout' parameters are not supported");
403+
404+
case ParameterConvention::Direct_Guaranteed:
405+
case ParameterConvention::Direct_Unowned: {
406+
cast<LoadableTypeInfo>(typeInfo).loadAsTake(IGF, alignedOffset, arguments);
407+
break;
408+
}
409+
410+
case ParameterConvention::Direct_Owned:
411+
// Copy the value out at +1.
412+
cast<LoadableTypeInfo>(typeInfo).loadAsCopy(IGF, alignedOffset, arguments);
413+
}
414+
415+
return {alignedOffset.getAddress(), typeInfo.getSize(IGF, paramTy)};
289416
}
290417

291418
void DistributedAccessor::emit() {
@@ -385,8 +512,18 @@ void DistributedAccessor::emit() {
385512

386513
// Deallocate all of the copied arguments.
387514
{
388-
for (auto &entry : AllocatedArguments)
389-
entry.TI.deallocateStack(IGF, entry.Addr, entry.Type);
515+
auto targetType = Target->getLoweredFunctionType();
516+
for (auto &alloca : AllocatedArguments) {
517+
const auto &param = targetType->getParameters()[alloca.ArgumentIdx];
518+
auto paramTy = param.getSILStorageInterfaceType();
519+
520+
if (paramTy.hasTypeParameter()) {
521+
IGF.emitDeallocateDynamicAlloca(alloca.Addr);
522+
} else {
523+
auto &typeInfo = IGF.getTypeInfo(paramTy);
524+
typeInfo.deallocateStack(IGF, alloca.Addr, paramTy);
525+
}
526+
}
390527
}
391528

392529
Explosion voidResult;

0 commit comments

Comments
 (0)