Skip to content

Commit f1bf7ba

Browse files
committed
[SE-0322] Temporary uninitialized buffers
Adds two new IRGen-level builtins (one for allocating, the other for deallocating), a stdlib shim function for enhanced stack-promotion heuristics, and the proposed public stdlib functions.
1 parent 87e4607 commit f1bf7ba

26 files changed

+906
-11
lines changed

include/swift/AST/Builtins.def

+23
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,29 @@ BUILTIN_MISC_OPERATION(AllocRaw, "allocRaw", "", Special)
603603
/// was allocated.
604604
BUILTIN_MISC_OPERATION(DeallocRaw, "deallocRaw", "", Special)
605605

606+
/// StackAlloc has type (Int, Int, Int) -> Builtin.RawPointer
607+
///
608+
/// Parameters: capacity, stride, alignment
609+
///
610+
/// The resulting pointer comes from the stack (as in the non-standard C
611+
/// extension `alloca()`.) It is at least as aligned as specified and is valid
612+
/// until the end of the calling scope.
613+
///
614+
/// The count and stride are multiplied together to get the byte count to use
615+
/// for the allocation.
616+
///
617+
/// The passed alignment must be a positive power of two. If the alignment value
618+
/// is not known at compile time, MaximumAlignment is assumed.
619+
BUILTIN_MISC_OPERATION(StackAlloc, "stackAlloc", "", Special)
620+
621+
/// StackDealloc has type (Builtin.RawPointer) -> ()
622+
///
623+
/// Parameters: address.
624+
///
625+
/// The range starting at `address`, previously allocated with
626+
/// Builtin.stackAlloc(), is deallocated from the stack.
627+
BUILTIN_MISC_OPERATION(StackDealloc, "stackDealloc", "", Special)
628+
606629
/// Fence has type () -> ().
607630
BUILTIN_MISC_OPERATION(Fence, "fence", "", None)
608631

include/swift/AST/DiagnosticsIRGen.def

+9
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,14 @@ ERROR(alignment_more_than_maximum,none,
5050
"@_alignment cannot increase alignment above maximum alignment of %0",
5151
(unsigned))
5252

53+
ERROR(temporary_allocation_size_negative,none,
54+
"allocation capacity must be greater than or equal to zero", ())
55+
ERROR(temporary_allocation_size_overflow,none,
56+
"allocation byte count too large", ())
57+
ERROR(temporary_allocation_alignment_not_positive,none,
58+
"alignment value must be greater than zero", ())
59+
ERROR(temporary_allocation_alignment_not_power_of_2,none,
60+
"alignment value must be a power of two", ())
61+
5362
#define UNDEFINE_DIAGNOSTIC_MACROS
5463
#include "DefineDiagnosticMacros.h"

lib/AST/Builtins.cpp

+17
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,18 @@ static ValueDecl *getDeallocOperation(ASTContext &ctx, Identifier id) {
10241024
_void);
10251025
}
10261026

1027+
static ValueDecl *getStackAllocOperation(ASTContext &ctx, Identifier id) {
1028+
return getBuiltinFunction(ctx, id, _thin,
1029+
_parameters(_word, _word, _word),
1030+
_rawPointer);
1031+
}
1032+
1033+
static ValueDecl *getStackDeallocOperation(ASTContext &ctx, Identifier id) {
1034+
return getBuiltinFunction(ctx, id, _thin,
1035+
_parameters(_rawPointer),
1036+
_void);
1037+
}
1038+
10271039
static ValueDecl *getFenceOperation(ASTContext &ctx, Identifier id) {
10281040
return getBuiltinFunction(ctx, id, _thin, _parameters(), _void);
10291041
}
@@ -2635,6 +2647,11 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
26352647
case BuiltinValueKind::DeallocRaw:
26362648
return getDeallocOperation(Context, Id);
26372649

2650+
case BuiltinValueKind::StackAlloc:
2651+
return getStackAllocOperation(Context, Id);
2652+
case BuiltinValueKind::StackDealloc:
2653+
return getStackDeallocOperation(Context, Id);
2654+
26382655
case BuiltinValueKind::CastToNativeObject:
26392656
case BuiltinValueKind::UnsafeCastToNativeObject:
26402657
case BuiltinValueKind::CastFromNativeObject:

lib/IRGen/GenOpaque.cpp

+10-4
Original file line numberDiff line numberDiff line change
@@ -533,15 +533,16 @@ irgen::emitInitializeBufferWithCopyOfBufferCall(IRGenFunction &IGF,
533533
StackAddress IRGenFunction::emitDynamicAlloca(SILType T,
534534
const llvm::Twine &name) {
535535
llvm::Value *size = emitLoadOfSize(*this, T);
536-
return emitDynamicAlloca(IGM.Int8Ty, size, Alignment(16), name);
536+
return emitDynamicAlloca(IGM.Int8Ty, size, Alignment(16), true, name);
537537
}
538538

539539
StackAddress IRGenFunction::emitDynamicAlloca(llvm::Type *eltTy,
540540
llvm::Value *arraySize,
541541
Alignment align,
542+
bool allowTaskAlloc,
542543
const llvm::Twine &name) {
543544
// Async functions call task alloc.
544-
if (isAsync()) {
545+
if (allowTaskAlloc && isAsync()) {
545546
llvm::Value *byteCount;
546547
auto eltSize = IGM.DataLayout.getTypeAllocSize(eltTy);
547548
if (eltSize == 1) {
@@ -556,6 +557,8 @@ StackAddress IRGenFunction::emitDynamicAlloca(llvm::Type *eltTy,
556557
return {address, address.getAddress()};
557558
// In coroutines, call llvm.coro.alloca.alloc.
558559
} else if (isCoroutine()) {
560+
// NOTE: llvm does not support dynamic allocas in coroutines.
561+
559562
// Compute the number of bytes to allocate.
560563
llvm::Value *byteCount;
561564
auto eltSize = IGM.DataLayout.getTypeAllocSize(eltTy);
@@ -606,9 +609,10 @@ StackAddress IRGenFunction::emitDynamicAlloca(llvm::Type *eltTy,
606609

607610
/// Deallocate dynamic alloca's memory if requested by restoring the stack
608611
/// location before the dynamic alloca's call.
609-
void IRGenFunction::emitDeallocateDynamicAlloca(StackAddress address) {
612+
void IRGenFunction::emitDeallocateDynamicAlloca(StackAddress address,
613+
bool allowTaskDealloc) {
610614
// Async function use taskDealloc.
611-
if (isAsync() && address.getAddress().isValid()) {
615+
if (allowTaskDealloc && isAsync() && address.getAddress().isValid()) {
612616
emitTaskDealloc(Address(address.getExtraInfo(), address.getAlignment()));
613617
return;
614618
}
@@ -617,6 +621,8 @@ void IRGenFunction::emitDeallocateDynamicAlloca(StackAddress address) {
617621
// for a partial_apply [stack] that did not need a context object on the
618622
// stack.
619623
else if (isCoroutine() && address.getAddress().isValid()) {
624+
// NOTE: llvm does not support dynamic allocas in coroutines.
625+
620626
auto allocToken = address.getExtraInfo();
621627
assert(allocToken && "dynamic alloca in coroutine without alloc token?");
622628
auto freeFn = llvm::Intrinsic::getDeclaration(

lib/IRGen/IRGenFunction.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,10 @@ class IRGenFunction {
222222

223223
StackAddress emitDynamicAlloca(SILType type, const llvm::Twine &name = "");
224224
StackAddress emitDynamicAlloca(llvm::Type *eltTy, llvm::Value *arraySize,
225-
Alignment align,
225+
Alignment align, bool allowTaskAlloc = true,
226226
const llvm::Twine &name = "");
227-
void emitDeallocateDynamicAlloca(StackAddress address);
227+
void emitDeallocateDynamicAlloca(StackAddress address,
228+
bool allowTaskDealloc = true);
228229

229230
llvm::BasicBlock *createBasicBlock(const llvm::Twine &Name);
230231
const TypeInfo &getTypeInfoForUnlowered(Type subst);

lib/IRGen/IRGenSIL.cpp

+133-5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#define DEBUG_TYPE "irgensil"
1919

2020
#include "swift/AST/ASTContext.h"
21+
#include "swift/AST/DiagnosticsIRGen.h"
2122
#include "swift/AST/IRGenOptions.h"
2223
#include "swift/AST/ParameterList.h"
2324
#include "swift/AST/Pattern.h"
@@ -57,6 +58,7 @@
5758
#include "llvm/IR/Intrinsics.h"
5859
#include "llvm/IR/Module.h"
5960
#include "llvm/Support/Debug.h"
61+
#include "llvm/Support/MathExtras.h"
6062
#include "llvm/Support/SaveAndRestore.h"
6163
#include "llvm/Transforms/Utils/Local.h"
6264

@@ -436,6 +438,11 @@ class IRGenSILFunction :
436438
/// Calculates EstimatedStackSize.
437439
void estimateStackSize();
438440

441+
inline bool isAddress(SILValue v) const {
442+
SILType type = v->getType();
443+
return type.isAddress() || type.getASTType() == IGM.Context.TheRawPointerType;
444+
}
445+
439446
void setLoweredValue(SILValue v, LoweredValue &&lv) {
440447
auto inserted = LoweredValues.insert({v, std::move(lv)});
441448
assert(inserted.second && "already had lowered value for sil value?!");
@@ -444,31 +451,31 @@ class IRGenSILFunction :
444451

445452
/// Create a new Address corresponding to the given SIL address value.
446453
void setLoweredAddress(SILValue v, const Address &address) {
447-
assert(v->getType().isAddress() && "address for non-address value?!");
454+
assert(isAddress(v) && "address for non-address value?!");
448455
setLoweredValue(v, address);
449456
}
450457

451458
void setLoweredStackAddress(SILValue v, const StackAddress &address) {
452-
assert(v->getType().isAddress() && "address for non-address value?!");
459+
assert(isAddress(v) && "address for non-address value?!");
453460
setLoweredValue(v, address);
454461
}
455462

456463
void setLoweredDynamicallyEnforcedAddress(SILValue v,
457464
const Address &address,
458465
llvm::Value *scratch) {
459-
assert(v->getType().isAddress() && "address for non-address value?!");
466+
assert(isAddress(v) && "address for non-address value?!");
460467
setLoweredValue(v, DynamicallyEnforcedAddress{address, scratch});
461468
}
462469

463470
void setContainerOfUnallocatedAddress(SILValue v,
464471
const Address &buffer) {
465-
assert(v->getType().isAddress() && "address for non-address value?!");
472+
assert(isAddress(v) && "address for non-address value?!");
466473
setLoweredValue(v,
467474
LoweredValue(buffer, LoweredValue::ContainerForUnallocatedAddress));
468475
}
469476

470477
void overwriteAllocatedAddress(SILValue v, const Address &address) {
471-
assert(v->getType().isAddress() && "address for non-address value?!");
478+
assert(isAddress(v) && "address for non-address value?!");
472479
auto it = LoweredValues.find(v);
473480
assert(it != LoweredValues.end() && "no existing entry for overwrite?");
474481
assert(it->second.isUnallocatedAddressInBuffer() &&
@@ -1623,6 +1630,9 @@ void LoweredValue::getExplosion(IRGenFunction &IGF, SILType type,
16231630
Explosion &ex) const {
16241631
switch (kind) {
16251632
case Kind::StackAddress:
1633+
ex.add(Storage.get<StackAddress>(kind).getAddressPointer());
1634+
return;
1635+
16261636
case Kind::ContainedAddress:
16271637
case Kind::DynamicallyEnforcedAddress:
16281638
case Kind::CoroutineState:
@@ -2958,9 +2968,127 @@ static std::unique_ptr<CallEmission> getCallEmissionForLoweredValue(
29582968
return callEmission;
29592969
}
29602970

2971+
/// Get the size passed to stackAlloc().
2972+
static llvm::Value *getStackAllocationSize(IRGenSILFunction &IGF,
2973+
SILValue vCapacity,
2974+
SILValue vStride,
2975+
SourceLoc loc) {
2976+
auto &Diags = IGF.IGM.Context.Diags;
2977+
2978+
// Check for a negative capacity, which is invalid.
2979+
auto capacity = IGF.getLoweredSingletonExplosion(vCapacity);
2980+
Optional<int64_t> capacityValue;
2981+
if (auto capacityConst = dyn_cast<llvm::ConstantInt>(capacity)) {
2982+
capacityValue = capacityConst->getSExtValue();
2983+
if (*capacityValue < 0) {
2984+
Diags.diagnose(loc, diag::temporary_allocation_size_negative);
2985+
}
2986+
}
2987+
2988+
// Check for a negative stride, which should never occur because the caller
2989+
// should always be using MemoryLayout<T>.stride to produce this value.
2990+
auto stride = IGF.getLoweredSingletonExplosion(vStride);
2991+
Optional<int64_t> strideValue;
2992+
if (auto strideConst = dyn_cast<llvm::ConstantInt>(stride)) {
2993+
strideValue = strideConst->getSExtValue();
2994+
if (*strideValue < 0) {
2995+
llvm_unreachable("Builtin.stackAlloc() caller passed an invalid stride");
2996+
}
2997+
}
2998+
2999+
// Get the byte count (the product of capacity and stride.)
3000+
llvm::Value *result = nullptr;
3001+
if (capacityValue && strideValue) {
3002+
int64_t byteCount = 0;
3003+
auto overflow = llvm::MulOverflow(*capacityValue, *strideValue, byteCount);
3004+
if (overflow) {
3005+
Diags.diagnose(loc, diag::temporary_allocation_size_overflow);
3006+
}
3007+
result = llvm::ConstantInt::get(IGF.IGM.SizeTy, byteCount);
3008+
3009+
} else {
3010+
// If either value is not known at compile-time, preconditions must be
3011+
// tested at runtime by Builtin.stackAlloc()'s caller. See
3012+
// _byteCountForTemporaryAllocation(of:capacity:).
3013+
result = IGF.Builder.CreateMul(capacity, stride);
3014+
}
3015+
3016+
// If the caller requests a zero-byte allocation, allocate one byte instead
3017+
// to ensure that the resulting pointer is valid and unique on the stack.
3018+
return IGF.Builder.CreateIntrinsicCall(llvm::Intrinsic::umax,
3019+
{IGF.IGM.SizeTy}, {llvm::ConstantInt::get(IGF.IGM.SizeTy, 1), result});
3020+
}
3021+
3022+
/// Get the alignment passed to stackAlloc() as a compile-time constant.
3023+
///
3024+
/// If the specified alignment is not known at compile time or is not valid,
3025+
/// the default maximum alignment is substituted.
3026+
static Alignment getStackAllocationAlignment(IRGenSILFunction &IGF,
3027+
SILValue v,
3028+
SourceLoc loc) {
3029+
auto &Diags = IGF.IGM.Context.Diags;
3030+
3031+
// Check for a non-positive alignment, which is invalid.
3032+
auto align = IGF.getLoweredSingletonExplosion(v);
3033+
if (auto alignConst = dyn_cast<llvm::ConstantInt>(align)) {
3034+
auto alignValue = alignConst->getSExtValue();
3035+
if (alignValue <= 0) {
3036+
Diags.diagnose(loc, diag::temporary_allocation_alignment_not_positive);
3037+
} else if (!llvm::isPowerOf2_64(alignValue)) {
3038+
Diags.diagnose(loc, diag::temporary_allocation_alignment_not_power_of_2);
3039+
} else {
3040+
return Alignment(alignValue);
3041+
}
3042+
}
3043+
3044+
// If the alignment is not known at compile-time, preconditions must be tested
3045+
// at runtime by Builtin.stackAlloc()'s caller. See
3046+
// _isStackAllocationSafe(byteCount:alignment:).
3047+
return Alignment(MaximumAlignment);
3048+
}
3049+
3050+
/// Emit a call to a stack allocation builtin (stackAlloc() or stackDealloc().)
3051+
///
3052+
/// Returns whether or not `i` was such a builtin (true if so, false if it was
3053+
/// some other builtin.)
3054+
static bool emitStackAllocBuiltinCall(IRGenSILFunction &IGF,
3055+
swift::BuiltinInst *i) {
3056+
if (i->getBuiltinKind() == BuiltinValueKind::StackAlloc) {
3057+
// Stack-allocate a buffer with the specified size/alignment.
3058+
auto loc = i->getLoc().getSourceLoc();
3059+
auto size = getStackAllocationSize(
3060+
IGF, i->getOperand(0), i->getOperand(1), loc);
3061+
auto align = getStackAllocationAlignment(IGF, i->getOperand(2), loc);
3062+
3063+
auto stackAddress = IGF.emitDynamicAlloca(IGF.IGM.Int8Ty, size, align,
3064+
false, "temp_alloc");
3065+
IGF.setLoweredStackAddress(i, stackAddress);
3066+
3067+
return true;
3068+
3069+
} else if (i->getBuiltinKind() == BuiltinValueKind::StackDealloc) {
3070+
// Deallocate a stack address previously allocated with the StackAlloc
3071+
// builtin above.
3072+
auto address = i->getOperand(0);
3073+
auto stackAddress = IGF.getLoweredStackAddress(address);
3074+
3075+
if (stackAddress.getAddress().isValid()) {
3076+
IGF.emitDeallocateDynamicAlloca(stackAddress, false);
3077+
}
3078+
3079+
return true;
3080+
}
3081+
3082+
return false;
3083+
}
3084+
29613085
void IRGenSILFunction::visitBuiltinInst(swift::BuiltinInst *i) {
29623086
const BuiltinInfo &builtin = getSILModule().getBuiltinInfo(i->getName());
29633087

3088+
if (emitStackAllocBuiltinCall(*this, i)) {
3089+
return;
3090+
}
3091+
29643092
auto argValues = i->getArguments();
29653093
Explosion args;
29663094
SmallVector<SILType, 4> argTypes;

lib/SIL/IR/OperandOwnership.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,8 @@ BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, SMulOver)
732732
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, SRem)
733733
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, GenericSRem)
734734
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, SSubOver)
735+
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, StackAlloc)
736+
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, StackDealloc)
735737
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, SToSCheckedTrunc)
736738
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, SToUCheckedTrunc)
737739
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, Expect)

lib/SIL/IR/SILInstruction.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,12 @@ bool SILInstruction::isAllocatingStack() const {
12641264
if (auto *PA = dyn_cast<PartialApplyInst>(this))
12651265
return PA->isOnStack();
12661266

1267+
if (auto *BI = dyn_cast<BuiltinInst>(this)) {
1268+
if (BI->getBuiltinKind() == BuiltinValueKind::StackAlloc) {
1269+
return true;
1270+
}
1271+
}
1272+
12671273
return false;
12681274
}
12691275

@@ -1275,6 +1281,13 @@ bool SILInstruction::isDeallocatingStack() const {
12751281
if (DRI->canAllocOnStack())
12761282
return true;
12771283
}
1284+
1285+
if (auto *BI = dyn_cast<BuiltinInst>(this)) {
1286+
if (BI->getBuiltinKind() == BuiltinValueKind::StackDealloc) {
1287+
return true;
1288+
}
1289+
}
1290+
12781291
return false;
12791292
}
12801293

lib/SIL/IR/ValueOwnership.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@ CONSTANT_OWNERSHIP_BUILTIN(None, Alignof)
487487
CONSTANT_OWNERSHIP_BUILTIN(None, AllocRaw)
488488
CONSTANT_OWNERSHIP_BUILTIN(None, AssertConf)
489489
CONSTANT_OWNERSHIP_BUILTIN(None, UToSCheckedTrunc)
490+
CONSTANT_OWNERSHIP_BUILTIN(None, StackAlloc)
491+
CONSTANT_OWNERSHIP_BUILTIN(None, StackDealloc)
490492
CONSTANT_OWNERSHIP_BUILTIN(None, SToSCheckedTrunc)
491493
CONSTANT_OWNERSHIP_BUILTIN(None, SToUCheckedTrunc)
492494
CONSTANT_OWNERSHIP_BUILTIN(None, UToUCheckedTrunc)

lib/SIL/Utils/MemAccessUtils.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -2154,6 +2154,8 @@ static void visitBuiltinAddress(BuiltinInst *builtin,
21542154
case BuiltinValueKind::CondFailMessage:
21552155
case BuiltinValueKind::AllocRaw:
21562156
case BuiltinValueKind::DeallocRaw:
2157+
case BuiltinValueKind::StackAlloc:
2158+
case BuiltinValueKind::StackDealloc:
21572159
case BuiltinValueKind::Fence:
21582160
case BuiltinValueKind::StaticReport:
21592161
case BuiltinValueKind::Once:

0 commit comments

Comments
 (0)