Skip to content

[embedded] Remove unspecialized functions before running IRGen #68781

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 9 commits into from
Sep 28, 2023
Merged
3 changes: 3 additions & 0 deletions include/swift/AST/SILOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ class SILOptions {
/// is a single SILModule in a single thread.
bool checkSILModuleLeaks = false;

/// Are we building in embedded Swift mode?
bool EmbeddedSwift = false;

/// The name of the file to which the backend should save optimization
/// records.
std::string OptRecordFile;
Expand Down
6 changes: 6 additions & 0 deletions include/swift/SIL/SILFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,12 @@ class SILFunction
return getLoweredFunctionType()->hasIndirectFormalResults();
}

// Returns true if the function has any generic arguments.
bool isGeneric() const {
auto s = getLoweredFunctionType()->getInvocationGenericSignature();
return s && !s->areAllParamsConcrete();
}

/// Returns true if this function ie either a class method, or a
/// closure that captures the 'self' value or its metatype.
///
Expand Down
1 change: 1 addition & 0 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3118,6 +3118,7 @@ bool CompilerInvocation::parseArgs(
IRGenOpts.InternalizeAtLink = true;
IRGenOpts.DisableLegacyTypeInfo = true;
SILOpts.CMOMode = CrossModuleOptimizationMode::Everything;
SILOpts.EmbeddedSwift = true;
}

return false;
Expand Down
21 changes: 14 additions & 7 deletions lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,18 @@ void IRGenerator::emitGlobalTopLevel(
// Emit SIL functions.
auto &m = PrimaryIGM->getSILModule();
for (SILFunction &f : m) {
// Generic functions should not be present in embedded Swift.
//
// TODO: Cannot enable this check yet because we first need removal of
// unspecialized classes and class vtables in SIL.
//
// if (SIL.getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
// if (f.isGeneric()) {
// llvm::errs() << "Unspecialized function: \n" << f << "\n";
// llvm_unreachable("unspecialized function present in embedded Swift");
// }
// }

if (isLazilyEmittedFunction(f, m))
continue;

Expand Down Expand Up @@ -1407,11 +1419,6 @@ deleteAndReenqueueForEmissionValuesDependentOnCanonicalPrespecializedMetadataRec
emitLazyTypeContextDescriptor(IGM, &decl, RequireMetadata);
}

static bool loweredFunctionHasGenericArguments(SILFunction *f) {
auto s = f->getLoweredFunctionType()->getInvocationGenericSignature();
return s && !s->areAllParamsConcrete();
}

/// Emit any lazy definitions (of globals or functions or whatever
/// else) that we require.
void IRGenerator::emitLazyDefinitions() {
Expand All @@ -1424,7 +1431,7 @@ void IRGenerator::emitLazyDefinitions() {
assert(LazyFieldDescriptors.empty());
// LazyFunctionDefinitions are allowed, but they must not be generic
for (SILFunction *f : LazyFunctionDefinitions) {
assert(!loweredFunctionHasGenericArguments(f));
assert(!f->isGeneric());
}
assert(LazyWitnessTables.empty());
assert(LazyCanonicalSpecializedMetadataAccessors.empty());
Expand Down Expand Up @@ -1554,7 +1561,7 @@ void IRGenerator::addLazyFunction(SILFunction *f) {

// Embedded Swift doesn't expect any generic functions to be referenced.
if (SIL.getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
assert(!loweredFunctionHasGenericArguments(f));
assert(!f->isGeneric());
}

assert(!FinishedEmittingLazyDefinitions);
Expand Down
16 changes: 15 additions & 1 deletion lib/SILOptimizer/IPO/DeadFunctionElimination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ class DeadFunctionAndGlobalElimination {

/// Checks is a function is alive, e.g. because it is visible externally.
bool isAnchorFunction(SILFunction *F) {
// In embedded Swift, (even public) generic functions *after serialization*
// cannot be used externally and are not anchors.
bool embedded = Module->getOptions().EmbeddedSwift;
bool generic = F->isGeneric();
bool isSerialized = Module->isSerialized();
if (embedded && generic && isSerialized)
return false;

// Functions that may be used externally cannot be removed.
if (F->isPossiblyUsedExternally())
Expand Down Expand Up @@ -148,6 +155,7 @@ class DeadFunctionAndGlobalElimination {

/// Marks a function as alive.
void makeAlive(SILFunction *F) {
LLVM_DEBUG(llvm::dbgs() << " makeAlive " << F->getName() << '\n');
AliveFunctionsAndTables.insert(F);
assert(F && "function does not exist");
Worklist.insert(F);
Expand Down Expand Up @@ -431,7 +439,11 @@ class DeadFunctionAndGlobalElimination {
F.forEachSpecializeAttrTargetFunction(
[this](SILFunction *targetFun) { ensureAlive(targetFun); });

if (!F.shouldOptimize()) {
bool retainBecauseFunctionIsNoOpt = !F.shouldOptimize();
if (Module->getOptions().EmbeddedSwift)
retainBecauseFunctionIsNoOpt = false;

if (retainBecauseFunctionIsNoOpt) {
LLVM_DEBUG(llvm::dbgs() << " anchor a no optimization function: "
<< F.getName() << "\n");
ensureAlive(&F);
Expand Down Expand Up @@ -526,6 +538,8 @@ class DeadFunctionAndGlobalElimination {

// Check vtable methods.
for (auto &vTable : Module->getVTables()) {
LLVM_DEBUG(llvm::dbgs() << " processing vtable "
<< vTable->getClass()->getName() << '\n');
Copy link
Contributor

@eeckstein eeckstein Sep 27, 2023

Choose a reason for hiding this comment

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

We have to be very careful with class methods. I'm not sure if DFE will now remove generic (non-final) vtable methods - which are not allowed in embedded swift. If we don't diagnose this earlier, calling such a method will result in a runtime error instead of a compiler error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, and this PR is not changing anything around classes and DFE on them (it doesn't happen). I just added this log to get some more visibility into what is considered to be anchors when debugging.

for (const SILVTable::Entry &entry : vTable->getEntries()) {
if (entry.getMethod().kind == SILDeclRef::Kind::Deallocator ||
entry.getMethod().kind == SILDeclRef::Kind::IVarDestroyer) {
Expand Down
12 changes: 6 additions & 6 deletions lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst,
RuntimeEffect impact = getRuntimeEffect(inst, impactType);
LocWithParent loc(inst->getLoc().getSourceLoc(), parentLoc);

if (module.getASTContext().LangOpts.hasFeature(Feature::Embedded) &&
if (module.getOptions().EmbeddedSwift &&
impact & RuntimeEffect::Existential) {
PrettyStackTracePerformanceDiagnostics stackTrace("existential", inst);
diagnose(loc, diag::performance_metadata, "existential");
Expand Down Expand Up @@ -507,7 +507,7 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst,
void PerformanceDiagnostics::checkNonAnnotatedFunction(SILFunction *function) {
for (SILBasicBlock &block : *function) {
for (SILInstruction &inst : block) {
if (function->getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
if (function->getModule().getOptions().EmbeddedSwift) {
auto loc = LocWithParent(inst.getLoc().getSourceLoc(), nullptr);
visitInst(&inst, PerformanceConstraints::None, &loc);
}
Expand Down Expand Up @@ -607,17 +607,17 @@ class PerformanceDiagnosticsPass : public SILModuleTransform {
}
}

if (!annotatedFunctionsFound &&
!getModule()->getASTContext().LangOpts.hasFeature(Feature::Embedded))
if (!annotatedFunctionsFound && !getModule()->getOptions().EmbeddedSwift)
return;

for (SILFunction &function : *module) {
// Don't rerun diagnostics on deserialized functions.
if (function.wasDeserializedCanonical())
continue;

// Don't check generic functions, they're about to be removed anyway.
if (getModule()->getASTContext().LangOpts.hasFeature(Feature::Embedded) &&
// Don't check generic functions in embedded Swift, they're about to be
// removed anyway.
if (getModule()->getOptions().EmbeddedSwift &&
function.getLoweredFunctionType()->getSubstGenericSignature())
continue;

Expand Down
7 changes: 7 additions & 0 deletions lib/SILOptimizer/PassManager/PassPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,13 @@ SILPassPipelinePlan::getOnonePassPipeline(const SILOptions &Options) {
// constant folded before any generic specialization.
P.addLateOnoneSimplification();

if (Options.EmbeddedSwift) {
// For embedded Swift: Remove all unspecialized functions. This is important
// to avoid having debuginfo references to these functions that we don't
// want to emit in IRGen.
P.addLateDeadFunctionAndGlobalElimination();
}

P.addCleanupDebugSteps();

// Has only an effect if the -sil-based-debuginfo option is specified.
Expand Down
2 changes: 1 addition & 1 deletion lib/SILOptimizer/Transforms/VTableSpecializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class VTableSpecializer : public SILModuleTransform {
void run() override {
SILModule &module = *getModule();

if (!module.getASTContext().LangOpts.hasFeature(Feature::Embedded)) return;
if (!module.getOptions().EmbeddedSwift) return;

LLVM_DEBUG(llvm::dbgs() << "***** VTableSpecializer\n");

Expand Down
2 changes: 1 addition & 1 deletion lib/SILOptimizer/UtilityPasses/Link.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class SILLinker : public SILModuleTransform {

// In embedded Swift, the stdlib contains all the runtime functions needed
// (swift_retain, etc.). Link them in so they can be referenced in IRGen.
if (M.getASTContext().LangOpts.hasFeature(Feature::Embedded)) {
if (M.getOptions().EmbeddedSwift) {
linkEmbeddedRuntimeFromStdlib();
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/embedded/classes-methods-no-stdlib.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class MySubClass: MyClass {
override func foo() { }
}

// CHECK: @"$s4main7MyClassCN" = {{.*}}<{ ptr, ptr, ptr, ptr, ptr }> <{ ptr null, ptr @"$s4main7MyClassCfD", ptr @"$s4main7MyClassC3fooyyF", ptr @"$s4main7MyClassC3baryyF", ptr @"$s4main7MyClassCACycfC" }>
// CHECK: @"$s4main7MyClassCN" = {{.*}}<{ ptr, ptr, ptr, ptr, ptr }> <{ ptr null, ptr @"$s4main7MyClassCfD", ptr @"$s4main7MyClassC3fooyyF", ptr @"$s4main7MyClassC3baryyF", ptr @swift_deletedMethodError }>
// CHECK: @"$s4main10MySubClassCN" = {{.*}}<{ ptr, ptr, ptr, ptr, ptr }> <{ ptr @"$s4main7MyClassCN", ptr @"$s4main10MySubClassCfD", ptr @"$s4main10MySubClassC3fooyyF", ptr @"$s4main7MyClassC3baryyF", ptr @"$s4main10MySubClassCACycfC" }>

// CHECK: define {{.*}}void @"$s4main4test1xyAA7MyClassC_tF"(ptr %0)
Expand Down
28 changes: 28 additions & 0 deletions test/embedded/debuginfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// RUN: %target-swift-frontend -g -emit-ir %s -enable-experimental-feature Embedded
// RUN: %target-swift-frontend -g -O -emit-ir %s -enable-experimental-feature Embedded
// RUN: %target-swift-frontend -g -Osize -emit-ir %s -enable-experimental-feature Embedded
// RUN: %target-swift-frontend -emit-ir %s -enable-experimental-feature Embedded
// RUN: %target-swift-frontend -O -emit-ir %s -enable-experimental-feature Embedded
// RUN: %target-swift-frontend -Osize -emit-ir %s -enable-experimental-feature Embedded

// REQUIRES: swift_in_compiler
// REQUIRES: VENDOR=apple
// REQUIRES: OS=macosx

public func foo<T>(_ array: inout [T]) {
array.withUnsafeMutableBytes {
$0[0] = 0
}
}

func foo2<T>(_ array: inout [T]) {
array.withUnsafeMutableBytes {
$0[0] = 0
}
}

public func test() {
var a: [UInt8] = [1, 2, 3]
foo(&a)
foo2(&a)
}