Skip to content

[embedded] Initial Swift Concurrency for embedded Swift #68928

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 8, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ extension BuiltinInst : OnoneSimplifyable {
.AssignCopyArrayBackToFront,
.AssignTakeArray,
.IsPOD:
optimizeFirstArgumentToThinMetatype(context)
optimizeArgumentToThinMetatype(argument: 0, context)
case .CreateAsyncTask:
// In embedded Swift, CreateAsyncTask needs a thin metatype
if context.options.enableEmbeddedSwift {
optimizeArgumentToThinMetatype(argument: 1, context)
}
default:
if let literal = constantFold(context) {
uses.replaceAll(with: literal, context)
Expand Down Expand Up @@ -174,16 +179,25 @@ private extension BuiltinInst {
context.erase(instruction: self)
}

func optimizeFirstArgumentToThinMetatype(_ context: SimplifyContext) {
guard let metatypeInst = operands[0].value as? MetatypeInst,
metatypeInst.type.representationOfMetatype(in: parentFunction) == .Thick else {
func optimizeArgumentToThinMetatype(argument: Int, _ context: SimplifyContext) {
let type: Type

if let metatypeInst = operands[argument].value as? MetatypeInst {
type = metatypeInst.type
} else if let initExistentialInst = operands[argument].value as? InitExistentialMetatypeInst {
type = initExistentialInst.metatype.type
} else {
return
}

guard type.representationOfMetatype(in: parentFunction) == .Thick else {
return
}

let instanceType = metatypeInst.type.instanceTypeOfMetatype(in: parentFunction)
let instanceType = type.instanceTypeOfMetatype(in: parentFunction)
let builder = Builder(before: self, context)
let metatype = builder.createMetatype(of: instanceType, representation: .Thin)
operands[0].set(to: metatype, context)
let newMetatype = builder.createMetatype(of: instanceType, representation: .Thin)
operands[argument].set(to: newMetatype, context)
}
}

Expand Down
2 changes: 2 additions & 0 deletions include/swift/ABI/MetadataValues.h
Original file line number Diff line number Diff line change
Expand Up @@ -2478,6 +2478,8 @@ enum class TaskOptionRecordKind : uint8_t {
AsyncLet = 2,
/// Request a child task for an 'async let'.
AsyncLetWithBuffer = 3,
/// Information about the result type of the task, used in embedded Swift.
ResultTypeInfo = 4,
/// Request a child task for swift_task_run_inline.
RunInline = UINT8_MAX,
};
Expand Down
75 changes: 68 additions & 7 deletions include/swift/ABI/Task.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,67 @@ class NullaryContinuationJob : public Job {
}
};

/// Descibes type information and offers value methods for an arbitrary concrete
/// type in a way that's compatible with regular Swift and embedded Swift. In
/// regular Swift, just holds a Metadata pointer and dispatches to the value
/// witness table. In embedded Swift, because we do not have any value witness
/// tables present at runtime, the witnesses are stored and referenced directly.
///
/// This structure is created from swift_task_create, where in regular Swift, the
/// compiler provides the Metadata pointer, and in embedded Swift, a
/// TaskOptionRecord is used to provide the witnesses.
struct ResultTypeInfo {
#if !SWIFT_CONCURRENCY_EMBEDDED
const Metadata *metadata = nullptr;
bool isNull() {
return metadata == nullptr;
}
size_t vw_size() {
return metadata->vw_size();
}
size_t vw_alignment() {
return metadata->vw_alignment();
}
void vw_initializeWithCopy(OpaqueValue *result, OpaqueValue *src) {
metadata->vw_initializeWithCopy(result, src);
}
void vw_storeEnumTagSinglePayload(OpaqueValue *v, unsigned whichCase,
unsigned emptyCases) {
metadata->vw_storeEnumTagSinglePayload(v, whichCase, emptyCases);
}
void vw_destroy(OpaqueValue *v) {
metadata->vw_destroy(v);
}
#else
size_t size = 0;
size_t alignMask = 0;
void (*initializeWithCopy)(OpaqueValue *result, OpaqueValue *src) = nullptr;
void (*storeEnumTagSinglePayload)(OpaqueValue *v, unsigned whichCase,
unsigned emptyCases) = nullptr;
void (*destroy)(OpaqueValue *) = nullptr;

bool isNull() {
return initializeWithCopy == nullptr;
}
size_t vw_size() {
return size;
}
size_t vw_alignment() {
return alignMask + 1;
}
void vw_initializeWithCopy(OpaqueValue *result, OpaqueValue *src) {
initializeWithCopy(result, src);
}
void vw_storeEnumTagSinglePayload(OpaqueValue *v, unsigned whichCase,
unsigned emptyCases) {
storeEnumTagSinglePayload(v, whichCase, emptyCases);
}
void vw_destroy(OpaqueValue *v) {
destroy(v);
}
#endif
};

/// An asynchronous task. Tasks are the analogue of threads for
/// asynchronous functions: that is, they are a persistent identity
/// for the overall async computation.
Expand Down Expand Up @@ -503,7 +564,7 @@ class AsyncTask : public Job {
std::atomic<WaitQueueItem> waitQueue;

/// The type of the result that will be produced by the future.
const Metadata *resultType;
ResultTypeInfo resultType;

SwiftError *error = nullptr;

Expand All @@ -513,14 +574,14 @@ class AsyncTask : public Job {
friend class AsyncTask;

public:
explicit FutureFragment(const Metadata *resultType)
explicit FutureFragment(ResultTypeInfo resultType)
: waitQueue(WaitQueueItem::get(Status::Executing, nullptr)),
resultType(resultType) { }

/// Destroy the storage associated with the future.
void destroy();

const Metadata *getResultType() const {
ResultTypeInfo getResultType() const {
return resultType;
}

Expand All @@ -534,7 +595,7 @@ class AsyncTask : public Job {
// `this` must have the same value modulo that alignment as
// `fragmentOffset` has in that function.
char *fragmentAddr = reinterpret_cast<char *>(this);
uintptr_t alignment = resultType->vw_alignment();
uintptr_t alignment = resultType.vw_alignment();
char *resultAddr = fragmentAddr + sizeof(FutureFragment);
uintptr_t unalignedResultAddrInt =
reinterpret_cast<uintptr_t>(resultAddr);
Expand All @@ -553,12 +614,12 @@ class AsyncTask : public Job {
/// Determine the size of the future fragment given the result type
/// of the future.
static size_t fragmentSize(size_t fragmentOffset,
const Metadata *resultType) {
ResultTypeInfo resultType) {
assert((fragmentOffset & (alignof(FutureFragment) - 1)) == 0);
size_t alignment = resultType->vw_alignment();
size_t alignment = resultType.vw_alignment();
size_t resultOffset = fragmentOffset + sizeof(FutureFragment);
resultOffset = (resultOffset + alignment - 1) & ~(alignment - 1);
size_t endOffset = resultOffset + resultType->vw_size();
size_t endOffset = resultOffset + resultType.vw_size();
return (endOffset - fragmentOffset);
}
};
Expand Down
15 changes: 15 additions & 0 deletions include/swift/ABI/TaskOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,21 @@ class AsyncLetWithBufferTaskOptionRecord : public TaskOptionRecord {
}
};

#if SWIFT_CONCURRENCY_EMBEDDED
class ResultTypeInfoTaskOptionRecord : public TaskOptionRecord {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be #if to only be for embedded, or is it useful outside of that context? Also, should this store a ResultTypeInfo rather than repeating its fields.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was fretting about this in the definition of ResultTypeInfo. Should we have ResultTypeInfo always have the five fields, so we have consistent layout between "embedded" and non-embedded? That makes our development easier, and maybe possibly allows some level of interoperability between embedded-ish and non-embedded code, but it wastes a couple of pointers worth of storage per task.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure there's any prospect of such interoperability. In any case, the embedded side doesn't have to be ABI stable so we could fix this up later if need be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made the class ifdef'd to embedded only, for now, and I'll explore the idea of using ResultTypeInfo in desktop Swift separately.

public:
size_t size;
size_t alignMask;
void (*initializeWithCopy)(OpaqueValue *, OpaqueValue *);
void (*storeEnumTagSinglePayload)(OpaqueValue *, unsigned, unsigned);
void (*destroy)(OpaqueValue *);

static bool classof(const TaskOptionRecord *record) {
return record->getKind() == TaskOptionRecordKind::ResultTypeInfo;
}
};
#endif

class RunInlineTaskOptionRecord : public TaskOptionRecord {
void *allocation;
size_t allocationBytes;
Expand Down
3 changes: 2 additions & 1 deletion include/swift/IRGen/Linking.h
Original file line number Diff line number Diff line change
Expand Up @@ -1012,7 +1012,8 @@ class LinkEntity {
}

static LinkEntity forValueWitness(CanType concreteType, ValueWitness witness) {
assert(!isEmbedded(concreteType));
// Explicitly allowed in embedded Swift because we generate value witnesses
// (but not witness tables) for Swift Concurrency usage.
Comment on lines +1015 to +1016
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we guard this on Swift concurrency being enabled? Maybe I'm being overly paranoid, but I could see some unrelated thing asking for value witness tables, and clients that don't enable concurrency shouldn't have to pay for anything (even vwt).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the right tradeoff is to catch emitting witness tables (for which the assert is still active, a few lines below), but allow individual value witness methods (this code). Even if we somehow had a bug where unexpected value witness methods are emitted, they will be dead-stripped if there isn't a table holding them. WDYT?

LinkEntity entity;
entity.Pointer = concreteType.getPointer();
entity.Data = LINKENTITY_SET_FIELD(Kind, unsigned(Kind::ValueWitness))
Expand Down
2 changes: 2 additions & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3133,6 +3133,8 @@ bool CompilerInvocation::parseArgs(
IRGenOpts.DisableLegacyTypeInfo = true;
IRGenOpts.ReflectionMetadata = ReflectionMetadataMode::None;
IRGenOpts.EnableReflectionNames = false;
TypeCheckerOpts.SkipFunctionBodies = FunctionBodySkipping::None;
SILOpts.SkipFunctionBodies = FunctionBodySkipping::None;
SILOpts.CMOMode = CrossModuleOptimizationMode::Everything;
SILOpts.EmbeddedSwift = true;
}
Expand Down
10 changes: 9 additions & 1 deletion lib/IRGen/GenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,15 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
(Builtin.ID == BuiltinValueKind::CreateAsyncTaskInGroup)
? args.claimNext()
: nullptr;
auto futureResultType = args.claimNext();

// In embedded Swift, futureResultType is a thin metatype, not backed by any
// actual value.
llvm::Value *futureResultType =
llvm::ConstantPointerNull::get(IGF.IGM.Int8PtrTy);
if (!IGF.IGM.Context.LangOpts.hasFeature(Feature::Embedded)) {
futureResultType = args.claimNext();
}

auto taskFunction = args.claimNext();
auto taskContext = args.claimNext();

Expand Down
84 changes: 71 additions & 13 deletions lib/IRGen/GenCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4470,18 +4470,17 @@ void irgen::emitTaskCancel(IRGenFunction &IGF, llvm::Value *task) {
call->setCallingConv(IGF.IGM.SwiftCC);
}

llvm::Value *irgen::emitTaskCreate(
IRGenFunction &IGF,
llvm::Value *flags,
llvm::Value *taskGroup,
llvm::Value *futureResultType,
llvm::Value *taskFunction,
llvm::Value *localContextInfo,
SubstitutionMap subs) {
// If there is a task group, emit a task group option structure to contain
// it.
llvm::Value *taskOptions = llvm::ConstantInt::get(
IGF.IGM.SwiftTaskOptionRecordPtrTy, 0);
llvm::Value *irgen::emitTaskCreate(IRGenFunction &IGF, llvm::Value *flags,
llvm::Value *taskGroup,
llvm::Value *futureResultType,
llvm::Value *taskFunction,
llvm::Value *localContextInfo,
SubstitutionMap subs) {
// Start with empty task options.
llvm::Value *taskOptions =
llvm::ConstantInt::get(IGF.IGM.SwiftTaskOptionRecordPtrTy, 0);

// If there is a task group, emit a task group option structure to contain it.
if (taskGroup) {
TaskOptionRecordFlags optionsFlags(TaskOptionRecordKind::TaskGroup);
llvm::Value *optionsFlagsVal = llvm::ConstantInt::get(
Expand All @@ -4492,14 +4491,73 @@ llvm::Value *irgen::emitTaskCreate(
"task_group_options");
auto optionsBaseRecord = IGF.Builder.CreateStructGEP(
optionsRecord, 0, Size());

// Flags
IGF.Builder.CreateStore(
optionsFlagsVal,
IGF.Builder.CreateStructGEP(optionsBaseRecord, 0, Size()));
// Parent
IGF.Builder.CreateStore(
taskOptions, IGF.Builder.CreateStructGEP(optionsBaseRecord, 1, Size()));

// TaskGroup
IGF.Builder.CreateStore(
taskGroup, IGF.Builder.CreateStructGEP(optionsRecord, 1, Size()));

taskOptions = IGF.Builder.CreateBitOrPointerCast(
optionsRecord.getAddress(), IGF.IGM.SwiftTaskOptionRecordPtrTy);
}

// In embedded Swift, create and pass result type info.
if (IGF.IGM.Context.LangOpts.hasFeature(Feature::Embedded)) {
auto optionsRecord =
IGF.createAlloca(IGF.IGM.SwiftResultTypeInfoTaskOptionRecordTy,
Alignment(), "result_type_info");
auto optionsBaseRecord =
IGF.Builder.CreateStructGEP(optionsRecord, 0, Size());

TaskOptionRecordFlags optionsFlags(TaskOptionRecordKind::ResultTypeInfo);
llvm::Value *optionsFlagsVal = llvm::ConstantInt::get(
IGF.IGM.SizeTy, optionsFlags.getOpaqueValue());

// Flags
IGF.Builder.CreateStore(
optionsFlagsVal,
IGF.Builder.CreateStructGEP(optionsBaseRecord, 0, Size()));
// Parent
IGF.Builder.CreateStore(
taskOptions, IGF.Builder.CreateStructGEP(optionsBaseRecord, 1, Size()));

Type unloweredType = subs.getReplacementTypes()[0];
SILType lowered = IGF.IGM.getLoweredType(unloweredType);
const TypeInfo &TI = IGF.IGM.getTypeInfo(lowered);
CanType canType = lowered.getASTType();
FixedPacking packing = TI.getFixedPacking(IGF.IGM);

// Size
IGF.Builder.CreateStore(
TI.getStaticSize(IGF.IGM),
IGF.Builder.CreateStructGEP(optionsRecord, 1, Size()));
// Align mask
IGF.Builder.CreateStore(
TI.getStaticAlignmentMask(IGF.IGM),
IGF.Builder.CreateStructGEP(optionsRecord, 2, Size()));
// initializeWithCopy witness
IGF.Builder.CreateStore(
IGF.IGM.getOrCreateValueWitnessFunction(
ValueWitness::InitializeWithCopy, packing, canType, lowered, TI),
IGF.Builder.CreateStructGEP(optionsRecord, 3, Size()));
// storeEnumTagSinglePayload witness
IGF.Builder.CreateStore(
IGF.IGM.getOrCreateValueWitnessFunction(
ValueWitness::StoreEnumTagSinglePayload, packing, canType, lowered,
TI),
IGF.Builder.CreateStructGEP(optionsRecord, 4, Size()));
// destroy witness
IGF.Builder.CreateStore(
IGF.IGM.getOrCreateValueWitnessFunction(ValueWitness::Destroy, packing,
canType, lowered, TI),
IGF.Builder.CreateStructGEP(optionsRecord, 5, Size()));

taskOptions = IGF.Builder.CreateBitOrPointerCast(
optionsRecord.getAddress(), IGF.IGM.SwiftTaskOptionRecordPtrTy);
}
Expand Down
18 changes: 13 additions & 5 deletions lib/IRGen/GenValueWitness.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ static Address getArgAsBuffer(IRGenFunction &IGF,
/// Don't add new callers of this, it doesn't make any sense.
static CanType getFormalTypeInPrimaryContext(CanType abstractType) {
auto *nominal = abstractType.getAnyNominal();
if (abstractType->isEqual(nominal->getDeclaredType())) {
if (nominal && abstractType->isEqual(nominal->getDeclaredType())) {
return nominal->mapTypeIntoContext(nominal->getDeclaredInterfaceType())
->getCanonicalType();
}
Expand Down Expand Up @@ -1260,12 +1260,20 @@ addValueWitness(IRGenModule &IGM, ConstantStructBuilder &B, ValueWitness index,
llvm_unreachable("bad value witness kind");

standard:
llvm::Function *fn = IGM.getOrCreateValueWitnessFunction(
index, packing, abstractType, concreteType, concreteTI);
addFunction(fn);
}

llvm::Function *IRGenModule::getOrCreateValueWitnessFunction(
ValueWitness index, FixedPacking packing, CanType abstractType,
SILType concreteType, const TypeInfo &type) {
llvm::Function *fn =
IGM.getAddrOfValueWitness(abstractType, index, ForDefinition);
getAddrOfValueWitness(abstractType, index, ForDefinition);
if (fn->empty())
buildValueWitnessFunction(IGM, fn, index, packing, abstractType,
concreteType, concreteTI);
addFunction(fn);
buildValueWitnessFunction(*this, fn, index, packing, abstractType,
concreteType, type);
return fn;
}

static bool shouldAddEnumWitnesses(CanType abstractType) {
Expand Down
9 changes: 9 additions & 0 deletions lib/IRGen/IRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,15 @@ IRGenModule::IRGenModule(IRGenerator &irgen,
SwiftTaskOptionRecordTy, // Base option record
SwiftTaskGroupPtrTy, // Task group
});
SwiftResultTypeInfoTaskOptionRecordTy = createStructType(
*this, "swift.result_type_info_task_option", {
SwiftTaskOptionRecordTy, // Base option record
SizeTy,
SizeTy,
Int8PtrTy,
Int8PtrTy,
Int8PtrTy,
});
ExecutorFirstTy = SizeTy;
ExecutorSecondTy = SizeTy;
SwiftExecutorTy = createStructType(*this, "swift.executor", {
Expand Down
Loading