mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
embedded: rewrite the diagnostic pass for embedded swift
1. move embedded diagnostics out of the PerformanceDiagnostics pass. It was completely separated from the other logic in this pass, anyway. 2. rewrite it in swift 3. fix several bugs, that means: missed diagnostics, which led to IRGen crashes * look at all methods in witness tables, including base protocols and associated conformances * visit all functions in the call tree, including generic functions with class bound generic arguments * handle all instructions, e.g. concurrency builtins 4. improve error messages by adding meaningful call-site information. For example: * if the error is in a specialized function, report where the generic function is originally specialized with concrete types * if the error is in a protocol witness method, report where the existential is created
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
swift_compiler_sources(Optimizer
|
||||
DiagnoseUnknownConstValues.swift
|
||||
EmbeddedSwiftDiagnostics.swift
|
||||
MandatoryPerformanceOptimizations.swift
|
||||
ReadOnlyGlobalVariables.swift
|
||||
StackProtection.swift
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
//===--- EmbeddedSwiftDiagnostics.swift -----------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
|
||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
//
|
||||
// See https://swift.org/LICENSE.txt for license information
|
||||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import AST
|
||||
import SIL
|
||||
|
||||
/// Diagnoses violations of Embedded Swift language restrictions.
|
||||
///
|
||||
let embeddedSwiftDiagnostics = ModulePass(name: "embedded-swift-diagnostics") {
|
||||
(moduleContext: ModulePassContext) in
|
||||
|
||||
guard moduleContext.options.enableEmbeddedSwift,
|
||||
// Skip all embedded diagnostics if asked. This is used from SourceKit to avoid reporting
|
||||
// false positives when WMO is turned off for indexing purposes.
|
||||
moduleContext.enableWMORequiredDiagnostics
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
// Try to start with public and exported functions to get better caller information in the diagnostics.
|
||||
let allFunctions = Array(moduleContext.functions.lazy.filter { !$0.isGeneric })
|
||||
.sorted(by: { $0.priority < $1.priority })
|
||||
|
||||
var checker = FunctionChecker(moduleContext)
|
||||
defer { checker.deinitialize() }
|
||||
|
||||
for function in allFunctions {
|
||||
do {
|
||||
assert(checker.callStack.isEmpty)
|
||||
try checker.checkFunction(function)
|
||||
} catch let error as Diagnostic {
|
||||
checker.diagnose(error)
|
||||
} catch {
|
||||
fatalError("unknown error thrown")
|
||||
}
|
||||
}
|
||||
|
||||
checkVTables(moduleContext)
|
||||
}
|
||||
|
||||
private struct FunctionChecker {
|
||||
let context: ModulePassContext
|
||||
var visitedFunctions = Set<Function>()
|
||||
var visitedConformances = Set<Conformance>()
|
||||
var callStack: Stack<CallSite>
|
||||
|
||||
init(_ context: ModulePassContext) {
|
||||
self.context = context
|
||||
self.callStack = Stack(context)
|
||||
}
|
||||
|
||||
mutating func deinitialize() {
|
||||
callStack.deinitialize()
|
||||
}
|
||||
|
||||
mutating func checkFunction(_ function: Function) throws {
|
||||
guard function.isDefinition,
|
||||
// Avoid infinite recursion
|
||||
visitedFunctions.insert(function).inserted
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
for inst in function.instructions {
|
||||
try checkInstruction(inst)
|
||||
}
|
||||
}
|
||||
|
||||
mutating func checkInstruction(_ instruction: Instruction) throws {
|
||||
switch instruction {
|
||||
case is OpenExistentialMetatypeInst,
|
||||
is InitExistentialMetatypeInst:
|
||||
throw Diagnostic(.embedded_swift_metatype_type, instruction.operands[0].value.type, at: instruction.location)
|
||||
|
||||
case is OpenExistentialBoxInst,
|
||||
is OpenExistentialBoxValueInst,
|
||||
is OpenExistentialValueInst,
|
||||
is OpenExistentialAddrInst,
|
||||
is InitExistentialAddrInst,
|
||||
is InitExistentialValueInst,
|
||||
is ExistentialMetatypeInst:
|
||||
throw Diagnostic(.embedded_swift_existential_type, instruction.operands[0].value.type, at: instruction.location)
|
||||
|
||||
case let aeb as AllocExistentialBoxInst:
|
||||
throw Diagnostic(.embedded_swift_existential_type, aeb.type, at: instruction.location)
|
||||
|
||||
case let ier as InitExistentialRefInst:
|
||||
for conf in ier.conformances {
|
||||
try checkConformance(conf, location: ier.location)
|
||||
}
|
||||
|
||||
case is ValueMetatypeInst,
|
||||
is MetatypeInst:
|
||||
let metaType = (instruction as! SingleValueInstruction).type
|
||||
if metaType.representationOfMetatype != .thin {
|
||||
let rawType = metaType.canonicalType.rawType.instanceTypeOfMetatype
|
||||
let type = rawType.isDynamicSelf ? rawType.staticTypeOfDynamicSelf : rawType
|
||||
if !type.isClass {
|
||||
throw Diagnostic(.embedded_swift_metatype_type, type, at: instruction.location)
|
||||
}
|
||||
}
|
||||
|
||||
case is KeyPathInst:
|
||||
throw Diagnostic(.embedded_swift_keypath, at: instruction.location)
|
||||
|
||||
case is CheckedCastAddrBranchInst,
|
||||
is UnconditionalCheckedCastAddrInst:
|
||||
throw Diagnostic(.embedded_swift_dynamic_cast, at: instruction.location)
|
||||
|
||||
case let abi as AllocBoxInst:
|
||||
// It needs a bit of work to support alloc_box of generic non-copyable structs/enums with deinit,
|
||||
// because we need to specialize the deinit functions, though they are not explicitly referenced in SIL.
|
||||
// Until this is supported, give an error in such cases. Otherwise IRGen would crash.
|
||||
if abi.allocsGenericValueTypeWithDeinit {
|
||||
throw Diagnostic(.embedded_capture_of_generic_value_with_deinit, at: abi.location)
|
||||
}
|
||||
fallthrough
|
||||
case is AllocRefInst,
|
||||
is AllocRefDynamicInst:
|
||||
if context.options.noAllocations {
|
||||
throw Diagnostic(.embedded_swift_allocating_type, (instruction as! SingleValueInstruction).type,
|
||||
at: instruction.location)
|
||||
}
|
||||
case is BeginApplyInst:
|
||||
throw Diagnostic(.embedded_swift_allocating_coroutine, at: instruction.location)
|
||||
case is ThunkInst:
|
||||
throw Diagnostic(.embedded_swift_allocating, at: instruction.location)
|
||||
|
||||
case let pai as PartialApplyInst:
|
||||
if context.options.noAllocations && !pai.isOnStack {
|
||||
throw Diagnostic(.embedded_swift_allocating_closure, at: instruction.location)
|
||||
}
|
||||
|
||||
case let bi as BuiltinInst:
|
||||
switch bi.id {
|
||||
case .AllocRaw:
|
||||
if context.options.noAllocations {
|
||||
throw Diagnostic(.embedded_swift_allocating, at: instruction.location)
|
||||
}
|
||||
case .BuildOrdinaryTaskExecutorRef,
|
||||
.BuildOrdinarySerialExecutorRef,
|
||||
.BuildComplexEqualitySerialExecutorRef:
|
||||
// Those builtins implicitly create an existential.
|
||||
try checkConformance(bi.substitutionMap.conformances[0], location: bi.location)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
case let apply as ApplySite:
|
||||
if context.options.noAllocations && apply.isAsync {
|
||||
throw Diagnostic(.embedded_swift_allocating_type, at: instruction.location)
|
||||
}
|
||||
|
||||
if !apply.callee.type.hasValidSignatureForEmbedded,
|
||||
// Some runtime functions have generic parameters in SIL, which are not used in IRGen.
|
||||
// Therefore exclude runtime functions at all.
|
||||
!apply.callsEmbeddedRuntimeFunction
|
||||
{
|
||||
switch apply.callee {
|
||||
case let cmi as ClassMethodInst:
|
||||
throw Diagnostic(.embedded_cannot_specialize_class_method, cmi.member, at: instruction.location)
|
||||
case let wmi as WitnessMethodInst:
|
||||
throw Diagnostic(.embedded_cannot_specialize_witness_method, wmi.member, at: instruction.location)
|
||||
default:
|
||||
throw Diagnostic(.embedded_call_generic_function, at: instruction.location)
|
||||
}
|
||||
}
|
||||
|
||||
// Although all (non-generic) functions are initially put into the worklist there are two reasons
|
||||
// to call `checkFunction` recursively:
|
||||
// * To get a better caller info in the diagnostics.
|
||||
// * When passing an opened existential to a generic function, it's valid in Embedded swift even if the
|
||||
// generic is not specialized. We need to check such generic functions, too.
|
||||
if let callee = apply.referencedFunction {
|
||||
callStack.push(CallSite(apply: apply, callee: callee))
|
||||
try checkFunction(callee)
|
||||
_ = callStack.pop()
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any violations in witness tables for existentials.
|
||||
mutating func checkConformance(_ conformance: Conformance, location: Location) throws {
|
||||
guard conformance.isConcrete,
|
||||
// Avoid infinite recursion
|
||||
visitedConformances.insert(conformance).inserted,
|
||||
let witnessTable = context.lookupWitnessTable(for: conformance)
|
||||
else {
|
||||
return
|
||||
}
|
||||
if !conformance.protocol.requiresClass {
|
||||
throw Diagnostic(.embedded_swift_existential_protocol, conformance.protocol.name, at: location)
|
||||
}
|
||||
|
||||
for entry in witnessTable.entries {
|
||||
switch entry {
|
||||
case .invalid, .associatedType:
|
||||
break
|
||||
case .method(let requirement, let witness):
|
||||
if let witness = witness {
|
||||
callStack.push(CallSite(location: location, kind: .conformance))
|
||||
if witness.isGeneric {
|
||||
throw Diagnostic(.embedded_cannot_specialize_witness_method, requirement, at: witness.location)
|
||||
}
|
||||
try checkFunction(witness)
|
||||
_ = callStack.pop()
|
||||
}
|
||||
case .baseProtocol(_, let witness):
|
||||
try checkConformance(witness, location: location)
|
||||
case .associatedConformance(_, let assocConf):
|
||||
// If it's not a class protocol, the associated type can never be used to create
|
||||
// an existential. Therefore this witness entry is never used at runtime in embedded swift.
|
||||
if assocConf.protocol.requiresClass {
|
||||
try checkConformance(assocConf, location: location)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutating func diagnose(_ error: Diagnostic) {
|
||||
var diagPrinted = false
|
||||
if error.position != nil {
|
||||
context.diagnosticEngine.diagnose(error)
|
||||
diagPrinted = true
|
||||
}
|
||||
|
||||
// If the original instruction doesn't have a location (e.g. because it's in a stdlib function),
|
||||
// search the callstack and use the location from a call site.
|
||||
while let callSite = callStack.pop() {
|
||||
if !diagPrinted {
|
||||
if callSite.location.hasValidLineNumber {
|
||||
context.diagnosticEngine.diagnose(error.id, error.arguments, at: callSite.location)
|
||||
diagPrinted = true
|
||||
}
|
||||
} else {
|
||||
// Print useful callsite information as a note (see `CallSite`)
|
||||
switch callSite.kind {
|
||||
case .constructorCall:
|
||||
context.diagnosticEngine.diagnose(.embedded_constructor_called, at: callSite.location)
|
||||
case .specializedCall:
|
||||
context.diagnosticEngine.diagnose(.embedded_specialization_called_from, at: callSite.location)
|
||||
case .conformance:
|
||||
context.diagnosticEngine.diagnose(.embedded_existential_created, at: callSite.location)
|
||||
case .call:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !diagPrinted {
|
||||
context.diagnosticEngine.diagnose(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print errors for generic functions in vtables, which is not allowed in embedded Swift.
|
||||
private func checkVTables(_ context: ModulePassContext) {
|
||||
for vTable in context.vTables {
|
||||
if !vTable.class.isGenericAtAnyLevel || vTable.isSpecialized {
|
||||
for entry in vTable.entries where entry.implementation.isGeneric {
|
||||
context.diagnosticEngine.diagnose(.embedded_cannot_specialize_class_method, entry.methodDecl,
|
||||
at: entry.methodDecl.location)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Relevant call site information for diagnostics.
|
||||
/// This information is printed as additional note(s) after the original diagnostic.
|
||||
private struct CallSite {
|
||||
enum Kind {
|
||||
// A regular function call. Not every function call in the call stack is printed in diagnostics.
|
||||
// This is only used if the original instruction doesn't have a location.
|
||||
case call
|
||||
|
||||
// If the error is in a constructor, this is the place where the object/value is created.
|
||||
case constructorCall
|
||||
|
||||
// If the error is in a specialized function, this is the place where the generic function is originally
|
||||
// specialized with concrete types. This is useful if a specialized type is relevant for the error.
|
||||
case specializedCall
|
||||
|
||||
// If the error is in a protocol witness method, this is the place where the existential is created.
|
||||
case conformance
|
||||
}
|
||||
|
||||
let location: Location
|
||||
let kind: Kind
|
||||
|
||||
init(apply: ApplySite, callee: Function) {
|
||||
self.location = apply.location
|
||||
if let d = callee.location.decl, d is ConstructorDecl {
|
||||
self.kind = .constructorCall
|
||||
} else if callee.isSpecialization && !apply.parentFunction.isSpecialization {
|
||||
self.kind = .specializedCall
|
||||
} else {
|
||||
self.kind = .call
|
||||
}
|
||||
}
|
||||
|
||||
init(location: Location, kind: Kind) {
|
||||
self.location = location
|
||||
self.kind = kind
|
||||
}
|
||||
}
|
||||
|
||||
private extension Function {
|
||||
// The priority (1 = highest) which defines the order in which functions are checked.
|
||||
// This is important to get good caller information in diagnostics.
|
||||
var priority: Int {
|
||||
// There might be functions without a location, e.g. `swift_readAtKeyPath` generated by SILGen for keypaths.
|
||||
// It's okay to skip the ctor/dtor/method detection logic for those.
|
||||
if location.hasValidLineNumber {
|
||||
if let decl = location.decl {
|
||||
if decl is DestructorDecl || decl is ConstructorDecl {
|
||||
return 4
|
||||
}
|
||||
if let parent = decl.parent, parent is ClassDecl {
|
||||
return 2
|
||||
}
|
||||
}
|
||||
}
|
||||
if isPossiblyUsedExternally {
|
||||
return 1
|
||||
}
|
||||
return 3
|
||||
}
|
||||
}
|
||||
|
||||
private extension AllocBoxInst {
|
||||
var allocsGenericValueTypeWithDeinit: Bool {
|
||||
type.getBoxFields(in: parentFunction).contains { $0.hasGenericValueDeinit(in: parentFunction) }
|
||||
}
|
||||
}
|
||||
|
||||
private extension ApplySite {
|
||||
var callsEmbeddedRuntimeFunction: Bool {
|
||||
if let callee = referencedFunction,
|
||||
!callee.isDefinition,
|
||||
!callee.name.startsWith("$e")
|
||||
{
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private extension Type {
|
||||
func hasGenericValueDeinit(in function: Function) -> Bool {
|
||||
guard isMoveOnly, let nominal = nominal else {
|
||||
return false
|
||||
}
|
||||
|
||||
if nominal.isGenericAtAnyLevel && nominal.valueTypeDestructor != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if isStruct {
|
||||
if let fields = getNominalFields(in: function) {
|
||||
return fields.contains { $0.hasGenericValueDeinit(in: function) }
|
||||
}
|
||||
} else if isEnum {
|
||||
if let enumCases = getEnumCases(in: function) {
|
||||
return enumCases.contains { $0.payload?.hasGenericValueDeinit(in: function) ?? false }
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@ private func registerSwiftPasses() {
|
||||
registerPass(diagnoseUnknownConstValues, { diagnoseUnknownConstValues.run($0)})
|
||||
registerPass(readOnlyGlobalVariablesPass, { readOnlyGlobalVariablesPass.run($0) })
|
||||
registerPass(stackProtection, { stackProtection.run($0) })
|
||||
registerPass(embeddedSwiftDiagnostics, { embeddedSwiftDiagnostics.run($0) })
|
||||
|
||||
// Function passes
|
||||
registerPass(asyncDemotion, { asyncDemotion.run($0) })
|
||||
|
||||
@@ -378,14 +378,16 @@ ERROR(bad_attr_on_non_const_global,none,
|
||||
"global variable must be a compile-time constant to use %0 attribute", (StringRef))
|
||||
ERROR(global_must_be_compile_time_const,none,
|
||||
"global variable must be a compile-time constant", ())
|
||||
ERROR(non_final_generic_class_function,none,
|
||||
"classes cannot have non-final generic functions in embedded Swift", ())
|
||||
ERROR(cannot_specialize_witness_method,none,
|
||||
"an existential type cannot contain a generic method %0 in embedded Swift", (DeclName))
|
||||
ERROR(embedded_cannot_specialize_class_method,none,
|
||||
"classes cannot have a non-final, generic method %0 in embedded Swift", (DeclName))
|
||||
ERROR(embedded_cannot_specialize_witness_method,none,
|
||||
"a protocol type cannot contain a generic method %0 in embedded Swift", (DeclName))
|
||||
ERROR(cannot_specialize_class,none,
|
||||
"cannot specialize %0 because class definition is not available (make sure to build with -wmo)", (Type))
|
||||
ERROR(embedded_swift_existential_type,none,
|
||||
"cannot use a value of protocol type %0 in embedded Swift", (Type))
|
||||
ERROR(embedded_swift_existential_protocol,none,
|
||||
"cannot use a value of protocol type 'any %0' in embedded Swift", (StringRef))
|
||||
ERROR(embedded_swift_existential,none,
|
||||
"cannot use a value of protocol type in embedded Swift", ())
|
||||
ERROR(perf_diag_existential_type,none,
|
||||
@@ -404,10 +406,22 @@ ERROR(embedded_swift_dynamic_cast,none,
|
||||
"cannot do dynamic casting in embedded Swift", ())
|
||||
ERROR(embedded_swift_allocating_type,none,
|
||||
"cannot use allocating type %0 in -no-allocations mode", (Type))
|
||||
ERROR(embedded_swift_allocating_coroutine,none,
|
||||
"cannot use co-routines (like accessors) in -no-allocations mode", ())
|
||||
ERROR(embedded_swift_allocating_closure,none,
|
||||
"cannot use escaping closures in -no-allocations mode", ())
|
||||
ERROR(embedded_swift_allocating,none,
|
||||
"cannot use allocating operation in -no-allocations mode", ())
|
||||
ERROR(embedded_capture_of_generic_value_with_deinit,none,
|
||||
"capturing generic non-copyable type with deinit in escaping closure not supported in embedded Swift", ())
|
||||
ERROR(embedded_call_generic_function,none,
|
||||
"cannot specialize generic function in this context", ())
|
||||
NOTE(embedded_specialization_called_from,none,
|
||||
"generic specialization called here", ())
|
||||
NOTE(embedded_existential_created,none,
|
||||
"protocol type value created here", ())
|
||||
NOTE(embedded_constructor_called,none,
|
||||
"instance of type created here", ())
|
||||
ERROR(wrong_linkage_for_serialized_function,none,
|
||||
"function has wrong linkage to be called from %0", (StringRef))
|
||||
NOTE(performance_called_from,none,
|
||||
|
||||
@@ -160,6 +160,8 @@ MODULE_PASS(StackProtection, "stack-protection",
|
||||
"Decides which functions need stack protectors")
|
||||
MODULE_PASS(DiagnoseUnknownConstValues, "diagnose-unknown-const-values",
|
||||
"Diagnose any '@const' entities which could not be simplified to a compile-time value")
|
||||
MODULE_PASS(EmbeddedSwiftDiagnostics, "embedded-swift-diagnostics",
|
||||
"Diagnose violations of Embedded Swift language restrictions")
|
||||
|
||||
IRGEN_PASS(AllocStackHoisting, "alloc-stack-hoisting",
|
||||
"SIL alloc_stack Hoisting")
|
||||
|
||||
@@ -94,10 +94,6 @@ public:
|
||||
return visitFunction(function, perfConstr, /*parentLoc*/ nullptr);
|
||||
}
|
||||
|
||||
bool visitFunctionEmbeddedSwift(SILFunction *function) {
|
||||
return visitFunctionEmbeddedSwift(function, /*parentLoc*/ nullptr);
|
||||
}
|
||||
|
||||
/// Check functions _without_ performance annotations.
|
||||
///
|
||||
/// This is need to check closure arguments of called performance-annotated
|
||||
@@ -108,9 +104,6 @@ private:
|
||||
bool visitFunction(SILFunction *function, PerformanceConstraints perfConstr,
|
||||
LocWithParent *parentLoc);
|
||||
|
||||
bool visitFunctionEmbeddedSwift(SILFunction *function,
|
||||
LocWithParent *parentLoc);
|
||||
|
||||
bool visitInst(SILInstruction *inst, PerformanceConstraints perfConstr,
|
||||
LocWithParent *parentLoc);
|
||||
|
||||
@@ -157,123 +150,6 @@ static bool isEffectFreeArraySemanticCall(SILInstruction *inst) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool hasGenericValueDeinit(SILType ty, SILFunction *f) {
|
||||
if (!ty.isMoveOnly())
|
||||
return false;
|
||||
NominalTypeDecl *nominal = ty.getNominalOrBoundGenericNominal();
|
||||
if (!nominal)
|
||||
return false;
|
||||
|
||||
if (nominal->getGenericSignature() && nominal->getValueTypeDestructor())
|
||||
return true;
|
||||
|
||||
if (isa<StructDecl>(nominal)) {
|
||||
for (unsigned i = 0, n = ty.getNumNominalFields(); i < n; ++i) {
|
||||
if (hasGenericValueDeinit(ty.getFieldType(i, f), f))
|
||||
return true;
|
||||
}
|
||||
} else if (auto *en = dyn_cast<EnumDecl>(nominal)) {
|
||||
for (EnumElementDecl *element : en->getAllElements()) {
|
||||
if (element->hasAssociatedValues()) {
|
||||
if (hasGenericValueDeinit(ty.getEnumElementType(element, f), f))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool allocsGenericValueTypeWithDeinit(AllocBoxInst *abi) {
|
||||
CanSILBoxType boxTy = abi->getBoxType();
|
||||
SILFunction *f = abi->getFunction();
|
||||
unsigned numFields = boxTy->getLayout()->getFields().size();
|
||||
for (unsigned fieldIdx = 0; fieldIdx < numFields; ++fieldIdx) {
|
||||
SILType fieldTy = getSILBoxFieldType(TypeExpansionContext(*f), boxTy,
|
||||
abi->getModule().Types, fieldIdx);
|
||||
if (hasGenericValueDeinit(fieldTy, f))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Prints Embedded Swift specific performance diagnostics (no existentials,
|
||||
/// no metatypes, optionally no allocations) for \p function.
|
||||
bool PerformanceDiagnostics::visitFunctionEmbeddedSwift(
|
||||
SILFunction *function, LocWithParent *parentLoc) {
|
||||
// Don't check generic functions in embedded Swift, they're about to be
|
||||
// removed anyway.
|
||||
if (function->isGeneric())
|
||||
return false;
|
||||
|
||||
if (!function->isDefinition())
|
||||
return false;
|
||||
|
||||
if (visitedFuncs.contains(function))
|
||||
return false;
|
||||
visitedFuncs[function] = PerformanceConstraints::None;
|
||||
|
||||
for (SILBasicBlock &block : *function) {
|
||||
for (SILInstruction &inst : block) {
|
||||
if (visitInst(&inst, PerformanceConstraints::None, parentLoc)) {
|
||||
if (inst.getLoc().getSourceLoc().isInvalid()) {
|
||||
auto demangledName = Demangle::demangleSymbolAsString(
|
||||
inst.getFunction()->getName(),
|
||||
Demangle::DemangleOptions::SimplifiedUIDemangleOptions());
|
||||
llvm::errs() << "in function " << demangledName << "\n";
|
||||
}
|
||||
LLVM_DEBUG(llvm::dbgs() << inst << *inst.getFunction());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto as = FullApplySite::isa(&inst)) {
|
||||
LocWithParent asLoc(inst.getLoc().getSourceLoc(), parentLoc);
|
||||
LocWithParent *loc = &asLoc;
|
||||
if (parentLoc &&
|
||||
asLoc.loc == inst.getFunction()->getLocation().getSourceLoc())
|
||||
loc = parentLoc;
|
||||
|
||||
for (SILFunction *callee : bca->getCalleeList(as)) {
|
||||
if (visitFunctionEmbeddedSwift(callee, loc))
|
||||
return true;
|
||||
}
|
||||
} else if (auto *bi = dyn_cast<BuiltinInst>(&inst)) {
|
||||
PrettyStackTracePerformanceDiagnostics stackTrace(
|
||||
"visitFunction::BuiltinInst (once, once with context)", &inst);
|
||||
|
||||
switch (bi->getBuiltinInfo().ID) {
|
||||
case BuiltinValueKind::Once:
|
||||
case BuiltinValueKind::OnceWithContext:
|
||||
if (auto *fri = dyn_cast<FunctionRefInst>(bi->getArguments()[1])) {
|
||||
LocWithParent asLoc(bi->getLoc().getSourceLoc(), parentLoc);
|
||||
LocWithParent *loc = &asLoc;
|
||||
if (parentLoc &&
|
||||
asLoc.loc == bi->getFunction()->getLocation().getSourceLoc())
|
||||
loc = parentLoc;
|
||||
|
||||
if (visitFunctionEmbeddedSwift(fri->getReferencedFunction(), loc))
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (auto *abi = dyn_cast<AllocBoxInst>(&inst)) {
|
||||
// It needs a bit of work to support alloc_box of generic non-copyable
|
||||
// structs/enums with deinit, because we need to specialize the deinit
|
||||
// functions, though they are not explicitly referenced in SIL.
|
||||
// Until this is supported, give an error in such cases. Otherwise
|
||||
// IRGen would crash.
|
||||
if (allocsGenericValueTypeWithDeinit(abi)) {
|
||||
LocWithParent loc(abi->getLoc().getSourceLoc(), parentLoc);
|
||||
diagnose(loc, diag::embedded_capture_of_generic_value_with_deinit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Prints performance diagnostics for \p function.
|
||||
bool PerformanceDiagnostics::visitFunction(SILFunction *function,
|
||||
PerformanceConstraints perfConstr,
|
||||
@@ -477,8 +353,7 @@ static bool metatypeUsesAreNotRelevant(MetatypeInst *mt) {
|
||||
if (auto *callee = apply->getReferencedFunctionOrNull()) {
|
||||
// Exclude `Swift._diagnoseUnexpectedEnumCaseValue<A, B>(type: A.Type, rawValue: B) -> Swift.Never`
|
||||
// It's a fatal error function, used for imported C enums.
|
||||
if (callee->getName() == "$ss32_diagnoseUnexpectedEnumCaseValue4type03rawE0s5NeverOxm_q_tr0_lF" &&
|
||||
!mt->getModule().getOptions().EmbeddedSwift) {
|
||||
if (callee->getName() == "$ss32_diagnoseUnexpectedEnumCaseValue4type03rawE0s5NeverOxm_q_tr0_lF") {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -488,25 +363,6 @@ static bool metatypeUsesAreNotRelevant(MetatypeInst *mt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool allowedMetadataUseInEmbeddedSwift(SILInstruction *inst) {
|
||||
// Only diagnose metatype, value_metatype instructions, ...
|
||||
if ((isa<ValueMetatypeInst>(inst) || isa<MetatypeInst>(inst))) {
|
||||
auto metaTy = cast<SingleValueInstruction>(inst)->getType().castTo<MetatypeType>();
|
||||
if (metaTy->getRepresentation() == MetatypeRepresentation::Thick) {
|
||||
Type instTy = metaTy->getInstanceType();
|
||||
if (auto selfType = instTy->getAs<DynamicSelfType>())
|
||||
instTy = selfType->getSelfType();
|
||||
// Class metadata are supported in embedded Swift
|
||||
return instTy->getClassOrBoundGenericClass() ? true : false;
|
||||
}
|
||||
// ... and alloc_ref_dynamic, for now.
|
||||
} else if (isa<AllocRefDynamicInst>(inst)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PerformanceDiagnostics::visitInst(SILInstruction *inst,
|
||||
PerformanceConstraints perfConstr,
|
||||
LocWithParent *parentLoc) {
|
||||
@@ -537,57 +393,6 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst,
|
||||
return true;
|
||||
}
|
||||
|
||||
if (module.getOptions().EmbeddedSwift) {
|
||||
// Explicitly don't detect RuntimeEffect::ExistentialClassBound - those are
|
||||
// allowed in Embedded Swift.
|
||||
if (impact & RuntimeEffect::Existential) {
|
||||
PrettyStackTracePerformanceDiagnostics stackTrace("existential", inst);
|
||||
if (impactType) {
|
||||
diagnose(loc, diag::embedded_swift_existential_type, impactType.getASTType());
|
||||
} else {
|
||||
diagnose(loc, diag::embedded_swift_existential);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (impact & RuntimeEffect::MetaData) {
|
||||
if (isa<ReleaseValueInst>(inst)) {
|
||||
// Move-only value types for which the deinit is not de-virtualized.
|
||||
diagnose(loc, diag::embedded_swift_value_deinit, impactType.getASTType());
|
||||
return true;
|
||||
}
|
||||
if (isa<KeyPathInst>(inst)) {
|
||||
diagnose(loc, diag::embedded_swift_keypath);
|
||||
return true;
|
||||
}
|
||||
if (isa<CheckedCastAddrBranchInst>(inst) || isa<UnconditionalCheckedCastAddrInst>(inst)) {
|
||||
diagnose(loc, diag::embedded_swift_dynamic_cast);
|
||||
return true;
|
||||
}
|
||||
if (!allowedMetadataUseInEmbeddedSwift(inst)) {
|
||||
PrettyStackTracePerformanceDiagnostics stackTrace("metatype", inst);
|
||||
if (impactType) {
|
||||
diagnose(loc, diag::embedded_swift_metatype_type, impactType.getASTType());
|
||||
} else {
|
||||
diagnose(loc, diag::embedded_swift_metatype);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (module.getOptions().NoAllocations) {
|
||||
if (impact & RuntimeEffect::Allocating) {
|
||||
PrettyStackTracePerformanceDiagnostics stackTrace("allocation", inst);
|
||||
if (impactType) {
|
||||
diagnose(loc, diag::embedded_swift_allocating_type, impactType.getASTType());
|
||||
} else {
|
||||
diagnose(loc, diag::embedded_swift_allocating);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (perfConstr == PerformanceConstraints::None ||
|
||||
perfConstr == PerformanceConstraints::NoExistentials ||
|
||||
perfConstr == PerformanceConstraints::NoObjCBridging)
|
||||
@@ -789,7 +594,7 @@ private:
|
||||
void run() override {
|
||||
SILModule *module = getModule();
|
||||
|
||||
// Skip all performance/embedded diagnostics if asked. This is used from
|
||||
// Skip all performance diagnostics if asked. This is used from
|
||||
// SourceKit to avoid reporting false positives when WMO is turned off for
|
||||
// indexing purposes.
|
||||
if (!module->getOptions().EnableWMORequiredDiagnostics) return;
|
||||
@@ -833,7 +638,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
if (!annotatedFunctionsFound && !getModule()->getOptions().EmbeddedSwift)
|
||||
if (!annotatedFunctionsFound)
|
||||
return;
|
||||
|
||||
for (SILFunction &function : *module) {
|
||||
@@ -845,57 +650,6 @@ private:
|
||||
diagnoser.checkNonAnnotatedFunction(&function);
|
||||
}
|
||||
}
|
||||
|
||||
if (getModule()->getOptions().EmbeddedSwift) {
|
||||
// Run embedded Swift SIL checks for metatype/existential use, and
|
||||
// allocation use (under -no-allocations mode). Try to start with public
|
||||
// and exported functions to get better call tree information.
|
||||
SmallVector<SILFunction *, 8> externallyVisibleFunctions;
|
||||
SmallVector<SILFunction *, 8> vtableMembers;
|
||||
SmallVector<SILFunction *, 8> others;
|
||||
SmallVector<SILFunction *, 8> constructorsAndDestructors;
|
||||
|
||||
for (SILFunction &function : *module) {
|
||||
// There might be SILFunctions without a location, e.g.
|
||||
// swift_readAtKeyPath generated by SILGen for keypaths. It's okay to
|
||||
// skip the ctor/dtor/method detection logic for those, such functions
|
||||
// still end up in the "others" list and are still visited.
|
||||
if (function.hasLocation()) {
|
||||
auto func =
|
||||
function.getLocation().getAsASTNode<AbstractFunctionDecl>();
|
||||
if (func) {
|
||||
if (isa<DestructorDecl>(func) || isa<ConstructorDecl>(func)) {
|
||||
constructorsAndDestructors.push_back(&function);
|
||||
continue;
|
||||
}
|
||||
if (getMethodDispatch(func) == MethodDispatch::Class) {
|
||||
vtableMembers.push_back(&function);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (function.isPossiblyUsedExternally()) {
|
||||
externallyVisibleFunctions.push_back(&function);
|
||||
continue;
|
||||
}
|
||||
|
||||
others.push_back(&function);
|
||||
}
|
||||
|
||||
for (SILFunction *function : externallyVisibleFunctions) {
|
||||
diagnoser.visitFunctionEmbeddedSwift(function);
|
||||
}
|
||||
for (SILFunction *function : vtableMembers) {
|
||||
diagnoser.visitFunctionEmbeddedSwift(function);
|
||||
}
|
||||
for (SILFunction *function : others) {
|
||||
diagnoser.visitFunctionEmbeddedSwift(function);
|
||||
}
|
||||
for (SILFunction *function : constructorsAndDestructors) {
|
||||
diagnoser.visitFunctionEmbeddedSwift(function);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -263,6 +263,7 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
|
||||
}
|
||||
|
||||
P.addDiagnoseUnknownConstValues();
|
||||
P.addEmbeddedSwiftDiagnostics();
|
||||
P.addPerformanceDiagnostics();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,26 +4,32 @@
|
||||
// REQUIRES: swift_feature_Embedded
|
||||
|
||||
public class MyClass {
|
||||
func foo<T>(t: T) { } // expected-error {{classes cannot have non-final generic functions in embedded Swift}}
|
||||
func bar() { }
|
||||
public func foo<T>(t: T) { } // expected-error {{classes cannot have a non-final, generic method 'foo(t:)' in embedded Swift}}
|
||||
public func bar() { }
|
||||
}
|
||||
|
||||
final class C2<Element> {
|
||||
// TODO: this shouldn't be a problem because the class is final
|
||||
init<T>(x: T) { } // expected-error {{classes cannot have non-final generic functions in embedded Swift}}
|
||||
init<T>(x: T) { }
|
||||
}
|
||||
|
||||
struct S {}
|
||||
|
||||
public func testMyClass(_ c: MyClass) {
|
||||
c.foo(t: S())
|
||||
c.bar()
|
||||
}
|
||||
|
||||
func testit2() -> C2<S> {
|
||||
return C2(x: S())
|
||||
}
|
||||
|
||||
open class C3<X> {
|
||||
public func foo<T>(t: T) {} // expected-error {{classes cannot have non-final generic functions in embedded Swift}}
|
||||
public func foo<T>(t: T) {} // expected-error {{classes cannot have a non-final, generic method 'foo(t:)' in embedded Swift}}
|
||||
}
|
||||
|
||||
func testit3() -> C3<S> {
|
||||
return C3<S>()
|
||||
let c = C3<S>()
|
||||
c.foo(t: S())
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,7 @@ func test(existential: any ClassBound) {
|
||||
}
|
||||
|
||||
func test(existential: any NotClassBound) {
|
||||
existential.foo() // expected-error {{cannot use a value of protocol type in embedded Swift}}
|
||||
// expected-note@+3 {{called from here}}
|
||||
existential.foo() // expected-error {{cannot use a value of protocol type 'any NotClassBound' in embedded Swift}}
|
||||
}
|
||||
|
||||
@main
|
||||
|
||||
@@ -9,20 +9,20 @@ public protocol P: AnyObject {
|
||||
}
|
||||
|
||||
final public class Class: P {
|
||||
public func foo<T>(t: T) {}
|
||||
public func foo<T>(t: T) {} // expected-error {{a protocol type cannot contain a generic method 'foo(t:)' in embedded Swift}}
|
||||
}
|
||||
|
||||
|
||||
public func testClass() -> P {
|
||||
return Class() // expected-error {{an existential type cannot contain a generic method 'foo(t:)' in embedded Swift}}
|
||||
return Class() // expected-note {{protocol type value created here}}
|
||||
}
|
||||
|
||||
final public class GenClass<X>: P {
|
||||
public func foo<T>(t: T) {}
|
||||
public func foo<T>(t: T) {} // expected-error {{a protocol type cannot contain a generic method 'foo(t:)' in embedded Swift}}
|
||||
}
|
||||
|
||||
|
||||
public func testGenClass() -> P {
|
||||
return GenClass<Int>() // expected-error {{an existential type cannot contain a generic method 'foo(t:)' in embedded Swift}}
|
||||
return GenClass<Int>() // expected-note {{protocol type value created here}}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ func test(existential: any ClassBound & OtherProtocol) {
|
||||
@main
|
||||
struct Main {
|
||||
static func main() {
|
||||
test(existential: MyClass()) // expected-error {{cannot use a value of protocol type 'any ClassBound & OtherProtocol' in embedded Swift}}
|
||||
// expected-note@-4 {{called from here}}
|
||||
test(existential: MyClass()) // expected-error {{cannot use a value of protocol type 'any OtherProtocol' in embedded Swift}}
|
||||
}
|
||||
}
|
||||
|
||||
17
test/embedded/existential-generic-error.swift
Normal file
17
test/embedded/existential-generic-error.swift
Normal file
@@ -0,0 +1,17 @@
|
||||
// RUN: %target-swift-emit-ir -parse-as-library -module-name main -verify %s -enable-experimental-feature Embedded -wmo
|
||||
|
||||
// REQUIRES: swift_in_compiler
|
||||
// REQUIRES: optimized_stdlib
|
||||
// REQUIRES: swift_feature_Embedded
|
||||
|
||||
public protocol MyProtocol: AnyObject {
|
||||
func foo<T: BinaryInteger>(ptr: UnsafeMutableRawPointer?, value: T)
|
||||
}
|
||||
|
||||
func test_some(p: some MyProtocol) {
|
||||
p.foo(ptr: nil, value: 0) // expected-error {{a protocol type cannot contain a generic method 'foo(ptr:value:)' in embedded Swift}}
|
||||
}
|
||||
|
||||
public func test_any(p: any MyProtocol) {
|
||||
test_some(p: p)
|
||||
}
|
||||
@@ -18,6 +18,6 @@ func castToExistential<T>(x: T) {
|
||||
}
|
||||
|
||||
public func callCastToExistential() {
|
||||
castToExistential(x: 42) // expected-note {{called from here}}
|
||||
castToExistential(x: 42) // expected-note {{generic specialization called here}}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
public class X {} // expected-error {{cannot use allocating type 'X' in -no-allocations mode}}
|
||||
public func use_a_class() -> X {
|
||||
let x = X() // expected-note {{called from here}}
|
||||
let x = X() // expected-note {{instance of type created here}}
|
||||
return x
|
||||
}
|
||||
|
||||
|
||||
@@ -20,5 +20,5 @@ public func test() {
|
||||
var args: (Int, Int, Int, Int) = (0, 0, 0, 0)
|
||||
let n = 2
|
||||
let value = 42
|
||||
unsafeWriteArray(type(of: args.0), array: &args, index: n, value: value) // expected-note {{called from here}}
|
||||
unsafeWriteArray(type(of: args.0), array: &args, index: n, value: value) // expected-note {{generic specialization called here}}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user