Skip to content

Commit 58fe89f

Browse files
committed
[NFC] Split up the "special convention" for runtime async functions
The special convention currently means three things: 1. There is no async FP symbol for the function. Calls should go directly to the function symbol, and an async context of fixed static size should be allocated. This is mandatory for calling runtime-provided async functions. 2. The callee context should be allocated but not initialized. The main context pointer passed should be the caller's context, and the continuation function pointer and callee context should be passed as separate arguments. The function will resume the continuation function pointer with the caller's context. This is a micro-optimization appropriate for functions that are expected to frequently return immediately; other functions shouldn't bother. 3. Generic arguments should be suppressed. This is a microoptimization for certain specific runtime functions where we happen to know that the runtime already stores the appropriate information internally. Other functions probably don't want this. Obviously, these different treatments should be split into different predicates so that functions can opt in to different subsets of them. I've also set the code up so that runtime functions can more easily request a specific static async context size. Previously, it was a confusingly embedded assumption that the static context size was always exactly two pointers more than the header.
1 parent 28fb3db commit 58fe89f

File tree

10 files changed

+328
-232
lines changed

10 files changed

+328
-232
lines changed

lib/IRGen/Callee.h

Lines changed: 132 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,7 @@ namespace irgen {
159159
}
160160
};
161161

162-
/// A function pointer value.
163-
class FunctionPointer {
162+
class FunctionPointerKind {
164163
public:
165164
enum class BasicKind {
166165
Function,
@@ -179,85 +178,124 @@ namespace irgen {
179178
DistributedExecuteTarget,
180179
};
181180

182-
class Kind {
183-
static constexpr unsigned SpecialOffset = 2;
184-
unsigned value;
185-
public:
186-
static constexpr BasicKind Function =
187-
BasicKind::Function;
188-
static constexpr BasicKind AsyncFunctionPointer =
189-
BasicKind::AsyncFunctionPointer;
190-
191-
Kind(BasicKind kind) : value(unsigned(kind)) {}
192-
Kind(SpecialKind kind) : value(unsigned(kind) + SpecialOffset) {}
193-
Kind(CanSILFunctionType fnType)
194-
: Kind(fnType->isAsync() ? BasicKind::AsyncFunctionPointer
195-
: BasicKind::Function) {}
196-
197-
BasicKind getBasicKind() const {
198-
return value < SpecialOffset ? BasicKind(value) : BasicKind::Function;
199-
}
200-
bool isAsyncFunctionPointer() const {
201-
return value == unsigned(BasicKind::AsyncFunctionPointer);
202-
}
203-
204-
bool isSpecial() const {
205-
return value >= SpecialOffset;
181+
private:
182+
static constexpr unsigned SpecialOffset = 2;
183+
unsigned value;
184+
public:
185+
static constexpr BasicKind Function =
186+
BasicKind::Function;
187+
static constexpr BasicKind AsyncFunctionPointer =
188+
BasicKind::AsyncFunctionPointer;
189+
190+
FunctionPointerKind(BasicKind kind)
191+
: value(unsigned(kind)) {}
192+
FunctionPointerKind(SpecialKind kind)
193+
: value(unsigned(kind) + SpecialOffset) {}
194+
FunctionPointerKind(CanSILFunctionType fnType)
195+
: FunctionPointerKind(fnType->isAsync()
196+
? BasicKind::AsyncFunctionPointer
197+
: BasicKind::Function) {}
198+
199+
static FunctionPointerKind defaultSync() {
200+
return BasicKind::Function;
201+
}
202+
static FunctionPointerKind defaultAsync() {
203+
return BasicKind::AsyncFunctionPointer;
204+
}
205+
206+
BasicKind getBasicKind() const {
207+
return value < SpecialOffset ? BasicKind(value) : BasicKind::Function;
208+
}
209+
bool isAsyncFunctionPointer() const {
210+
return value == unsigned(BasicKind::AsyncFunctionPointer);
211+
}
212+
213+
bool isSpecial() const {
214+
return value >= SpecialOffset;
215+
}
216+
SpecialKind getSpecialKind() const {
217+
assert(isSpecial());
218+
return SpecialKind(value - SpecialOffset);
219+
}
220+
221+
222+
/// Given that this is an async function, does it have a
223+
/// statically-specified size for its async context?
224+
///
225+
/// Returning a non-None value is necessary for special functions
226+
/// defined in the runtime. Without this, we'll attempt to load
227+
/// the context size from an async FP symbol which the runtime
228+
/// doesn't actually emit.
229+
Optional<Size> getStaticAsyncContextSize(IRGenModule &IGM) const;
230+
231+
/// Given that this is an async function, should we pass the
232+
/// continuation function pointer and context directly to it
233+
/// rather than building a frame?
234+
///
235+
/// This is a micro-optimization that is reasonable for functions
236+
/// that are expected to return immediately in a common fast path.
237+
/// Other functions should not do this.
238+
bool shouldPassContinuationDirectly() const {
239+
if (!isSpecial()) return false;
240+
241+
switch (getSpecialKind()) {
242+
case SpecialKind::TaskFutureWaitThrowing:
243+
case SpecialKind::TaskFutureWait:
244+
case SpecialKind::AsyncLetWait:
245+
case SpecialKind::AsyncLetWaitThrowing:
246+
case SpecialKind::AsyncLetGet:
247+
case SpecialKind::AsyncLetGetThrowing:
248+
case SpecialKind::AsyncLetFinish:
249+
case SpecialKind::TaskGroupWaitNext:
250+
case SpecialKind::DistributedExecuteTarget:
251+
return true;
206252
}
207-
SpecialKind getSpecialKind() const {
208-
assert(isSpecial());
209-
return SpecialKind(value - SpecialOffset);
210-
}
211-
212-
bool isSpecialAsyncLet() const {
213-
if (!isSpecial()) return false;
214-
switch (getSpecialKind()) {
215-
case SpecialKind::AsyncLetGet:
216-
case SpecialKind::AsyncLetGetThrowing:
217-
case SpecialKind::AsyncLetFinish:
218-
return true;
219-
220-
case SpecialKind::TaskFutureWaitThrowing:
221-
case SpecialKind::TaskFutureWait:
222-
case SpecialKind::AsyncLetWait:
223-
case SpecialKind::AsyncLetWaitThrowing:
224-
case SpecialKind::TaskGroupWaitNext:
225-
case SpecialKind::DistributedExecuteTarget:
226-
return false;
227-
}
228-
229-
return false;
253+
llvm_unreachable("covered switch");
254+
}
255+
256+
/// Should we suppress passing arguments associated with the generic
257+
/// signature from the given function?
258+
///
259+
/// This is a micro-optimization for certain runtime functions that
260+
/// are known to not need the generic arguments, probably because
261+
/// they've already been stored elsewhere.
262+
///
263+
/// This may only work for async function types right now. If so,
264+
/// that's a totally unnecessary restriction which should be easy
265+
/// to lift, if you have a sync runtime function that would benefit
266+
/// from this.
267+
bool shouldSuppressPolymorphicArguments() const {
268+
if (!isSpecial()) return false;
269+
270+
switch (getSpecialKind()) {
271+
case SpecialKind::TaskFutureWaitThrowing:
272+
case SpecialKind::TaskFutureWait:
273+
case SpecialKind::AsyncLetWait:
274+
case SpecialKind::AsyncLetWaitThrowing:
275+
case SpecialKind::AsyncLetGet:
276+
case SpecialKind::AsyncLetGetThrowing:
277+
case SpecialKind::AsyncLetFinish:
278+
case SpecialKind::TaskGroupWaitNext:
279+
case SpecialKind::DistributedExecuteTarget:
280+
return true;
230281
}
282+
llvm_unreachable("covered switch");
283+
}
231284

232-
/// Should we suppress the generic signature from the given function?
233-
///
234-
/// This is a micro-optimization we apply to certain special functions
235-
/// that we know don't need generics.
236-
bool useSpecialConvention() const {
237-
if (!isSpecial()) return false;
238-
239-
switch (getSpecialKind()) {
240-
case SpecialKind::TaskFutureWaitThrowing:
241-
case SpecialKind::TaskFutureWait:
242-
case SpecialKind::AsyncLetWait:
243-
case SpecialKind::AsyncLetWaitThrowing:
244-
case SpecialKind::AsyncLetGet:
245-
case SpecialKind::AsyncLetGetThrowing:
246-
case SpecialKind::AsyncLetFinish:
247-
case SpecialKind::TaskGroupWaitNext:
248-
case SpecialKind::DistributedExecuteTarget:
249-
return true;
250-
}
251-
llvm_unreachable("covered switch");
252-
}
285+
friend bool operator==(FunctionPointerKind lhs, FunctionPointerKind rhs) {
286+
return lhs.value == rhs.value;
287+
}
288+
friend bool operator!=(FunctionPointerKind lhs, FunctionPointerKind rhs) {
289+
return !(lhs == rhs);
290+
}
291+
};
253292

254-
friend bool operator==(Kind lhs, Kind rhs) {
255-
return lhs.value == rhs.value;
256-
}
257-
friend bool operator!=(Kind lhs, Kind rhs) {
258-
return !(lhs == rhs);
259-
}
260-
};
293+
/// A function pointer value.
294+
class FunctionPointer {
295+
public:
296+
using Kind = FunctionPointerKind;
297+
using BasicKind = Kind::BasicKind;
298+
using SpecialKind = Kind::SpecialKind;
261299

262300
private:
263301
Kind kind;
@@ -388,11 +426,15 @@ namespace irgen {
388426
/// Form a FunctionPointer whose Kind is ::Function.
389427
FunctionPointer getAsFunction(IRGenFunction &IGF) const;
390428

391-
bool useStaticContextSize() const {
392-
return !kind.isAsyncFunctionPointer();
429+
Optional<Size> getStaticAsyncContextSize(IRGenModule &IGM) const {
430+
return kind.getStaticAsyncContextSize(IGM);
431+
}
432+
bool shouldPassContinuationDirectly() const {
433+
return kind.shouldPassContinuationDirectly();
434+
}
435+
bool shouldSuppressPolymorphicArguments() const {
436+
return kind.shouldSuppressPolymorphicArguments();
393437
}
394-
395-
bool useSpecialConvention() const { return kind.useSpecialConvention(); }
396438
};
397439

398440
class Callee {
@@ -456,7 +498,15 @@ namespace irgen {
456498
return Fn.getSignature();
457499
}
458500

459-
bool useSpecialConvention() const { return Fn.useSpecialConvention(); }
501+
Optional<Size> getStaticAsyncContextSize(IRGenModule &IGM) const {
502+
return Fn.getStaticAsyncContextSize(IGM);
503+
}
504+
bool shouldPassContinuationDirectly() const {
505+
return Fn.shouldPassContinuationDirectly();
506+
}
507+
bool shouldSuppressPolymorphicArguments() const {
508+
return Fn.shouldSuppressPolymorphicArguments();
509+
}
460510

461511
/// If this callee has a value for the Swift context slot, return
462512
/// it; otherwise return non-null.

0 commit comments

Comments
 (0)