Skip to content

Add -Xfrontend -mergeable-traps as a way to emit mergeable traps #76045

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 7 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ private func runMergeCondFails(function: Function, context: FunctionPassContext)

for inst in block.instructions {
if let cfi = inst as? CondFailInst {
let messageIsSame = condFailToMerge.isEmpty || cfi.message == condFailToMerge.first!.message
let forceAllowMerge = context.options.enableMergeableTraps

// Do not process arithmetic overflow checks. We typically generate more
// efficient code with separate jump-on-overflow.
if !hasOverflowConditionOperand(cfi) &&
(condFailToMerge.isEmpty || cfi.message == condFailToMerge.first!.message) {
if !hasOverflowConditionOperand(cfi) && (messageIsSame || forceAllowMerge) {
condFailToMerge.push(cfi)
}
} else if inst.mayHaveSideEffects || inst.mayReadFromMemory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ struct Options {
_bridged.hasFeature(.Embedded)
}

var enableMergeableTraps: Bool {
_bridged.enableMergeableTraps()
}

func hasFeature(_ feature: BridgedFeature) -> Bool {
_bridged.hasFeature(feature)
}
Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/IRGenOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,9 @@ class IRGenOptions {
// variants.
unsigned UseCoroCCArm64 : 1;

// Whether to emit mergeable or non-mergeable traps.
unsigned MergeableTraps : 1;

/// The number of threads for multi-threaded code generation.
unsigned NumThreads = 0;

Expand Down Expand Up @@ -646,6 +649,7 @@ class IRGenOptions {
EmitAsyncFramePushPopMetadata(true), EmitTypeMallocForCoroFrame(false),
AsyncFramePointerAll(false), UseProfilingMarkerThunks(false),
UseCoroCCX8664(false), UseCoroCCArm64(false),
MergeableTraps(false),
DebugInfoForProfiling(false), CmdArgs(),
SanitizeCoverage(llvm::SanitizerCoverageOptions()),
TypeInfoFilter(TypeInfoDumpFilter::All),
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/SILOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@ class SILOptions {
/// Temporarily used to bootstrap the AddressableParameters feature.
bool EnableAddressDependencies = true;

// Whether to allow merging traps and cond_fails.
bool MergeableTraps = false;

SILOptions() {}

/// Return a hash code of any components from these options that should
Expand Down
4 changes: 4 additions & 0 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -1401,6 +1401,10 @@ def disable_split_cold_code :
Flag<["-"], "disable-split-cold-code">,
HelpText<"Disable splitting of cold code when optimizing">;

def mergeable_traps :
Flag<["-"], "mergeable-traps">,
HelpText<"Emit mergeable traps even in optimized builds">;

def enable_new_llvm_pass_manager :
Flag<["-"], "enable-new-llvm-pass-manager">,
HelpText<"Enable the new llvm pass manager">;
Expand Down
1 change: 1 addition & 0 deletions include/swift/SILOptimizer/OptimizerBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ struct BridgedPassContext {

BRIDGED_INLINE bool useAggressiveReg2MemForCodeSize() const;
BRIDGED_INLINE bool enableStackProtection() const;
BRIDGED_INLINE bool enableMergeableTraps() const;
BRIDGED_INLINE bool hasFeature(BridgedFeature feature) const;
BRIDGED_INLINE bool enableMoveInoutStackProtection() const;
BRIDGED_INLINE AssertConfiguration getAssertConfiguration() const;
Expand Down
5 changes: 5 additions & 0 deletions include/swift/SILOptimizer/OptimizerBridgingImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,11 @@ bool BridgedPassContext::enableStackProtection() const {
return mod->getOptions().EnableStackProtection;
}

bool BridgedPassContext::enableMergeableTraps() const {
swift::SILModule *mod = invocation->getPassManager()->getModule();
return mod->getOptions().MergeableTraps;
}

bool BridgedPassContext::hasFeature(BridgedFeature feature) const {
swift::SILModule *mod = invocation->getPassManager()->getModule();
return mod->getASTContext().LangOpts.hasFeature((swift::Feature)feature);
Expand Down
5 changes: 5 additions & 0 deletions lib/DriverTool/sil_opt_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,10 @@ struct SILOptOptions {
llvm::cl::opt<bool> EnableAddressDependencies = llvm::cl::opt<bool>(
"enable-address-dependencies",
llvm::cl::desc("Enable enforcement of lifetime dependencies on addressable values."));

llvm::cl::opt<bool> MergeableTraps = llvm::cl::opt<bool>(
"mergeable-traps",
llvm::cl::desc("Enable cond_fail merging."));
};

/// Regular expression corresponding to the value given in one of the
Expand Down Expand Up @@ -914,6 +918,7 @@ int sil_opt_main(ArrayRef<const char *> argv, void *MainAddr) {
options.EnablePackMetadataStackPromotion;

SILOpts.EnableAddressDependencies = options.EnableAddressDependencies;
SILOpts.MergeableTraps = options.MergeableTraps;

if (options.OptModeFlag == OptimizationMode::NotSet) {
if (options.OptimizationGroup == OptGroup::Diagnostics)
Expand Down
6 changes: 4 additions & 2 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3109,11 +3109,11 @@ static bool ParseSILArgs(SILOptions &Opts, ArgList &Args,
Opts.ShouldFunctionsBePreservedToDebugger &=
LTOKind.value() == IRGenLLVMLTOKind::None;


Opts.EnableAddressDependencies =
Opts.EnableAddressDependencies =
Args.hasFlag(OPT_enable_address_dependencies,
OPT_disable_address_dependencies,
Opts.EnableAddressDependencies);
Opts.MergeableTraps = Args.hasArg(OPT_mergeable_traps);

return false;
}
Expand Down Expand Up @@ -3782,6 +3782,8 @@ static bool ParseIRGenArgs(IRGenOptions &Opts, ArgList &Args,
return true;
}

Opts.MergeableTraps = Args.hasArg(OPT_mergeable_traps);

Opts.EnableObjectiveCProtocolSymbolicReferences =
Args.hasFlag(OPT_enable_objective_c_protocol_symbolic_references,
OPT_disable_objective_c_protocol_symbolic_references,
Expand Down
2 changes: 1 addition & 1 deletion lib/IRGen/IRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ llvm::CallInst *IRBuilder::CreateNonMergeableTrap(IRGenModule &IGM,
}
}

if (IGM.IRGen.Opts.shouldOptimize()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we keep IGM.IRGen.Opts.shouldOptimize(), too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, done!

if (IGM.IRGen.Opts.shouldOptimize() && !IGM.IRGen.Opts.MergeableTraps) {
// Emit unique side-effecting inline asm calls in order to eliminate
// the possibility that an LLVM optimization or code generation pass
// will merge these blocks back together again. We emit an empty asm
Expand Down
44 changes: 44 additions & 0 deletions test/SILOptimizer/merge_cond_fail.sil
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %target-sil-opt -enable-sil-verify-all %s -merge-cond_fails | %FileCheck %s
// RUN: %target-sil-opt -enable-sil-verify-all %s -merge-cond_fails -mergeable-traps | %FileCheck %s --check-prefix CHECK-FORCE-MERGE

// REQUIRES: swift_in_compiler

Expand Down Expand Up @@ -36,3 +37,46 @@ bb0 (%0 : $Builtin.Int1, %1 : $Builtin.Int1, %2 : $Builtin.Int1, %3: $Builtin.In
cond_fail %10 : $Builtin.Int1
return %9 : $Builtin.Int64
}

// CHECK-LABEL: sil @nonmergeable_cond_fail
// CHECK: bb0([[ARG1:%.*]] : $Builtin.Int1, [[ARG2:%.*]] : $Builtin.Int1, [[ARG3:%.*]] : $Builtin.Int1, [[ARG4:%.*]] : $Builtin.Int1, [[ARG5:%.*]] : $*Builtin.Int64):
// CHECK: {{ cond_fail}}
// CHECK: {{ cond_fail}}
// CHECK: [[LD:%.*]] = load [[ARG5]]
// CHECK: {{ cond_fail}}
// CHECK: {{ cond_fail}}
// CHECK: [[TUPLE:%.*]] = builtin "sadd_with_overflow_Int64"
// CHECK: [[RES:%.*]] = tuple_extract [[TUPLE]]{{.*}}, 0
// CHECK: [[OVERFLOW:%.*]] = tuple_extract [[TUPLE]]{{.*}}, 1
// CHECK: {{ cond_fail}}
// CHECK: return [[RES]]

// CHECK-FORCE-MERGE-LABEL: sil @nonmergeable_cond_fail
// CHECK-FORCE-MERGE: bb0([[ARG1:%.*]] : $Builtin.Int1, [[ARG2:%.*]] : $Builtin.Int1, [[ARG3:%.*]] : $Builtin.Int1, [[ARG4:%.*]] : $Builtin.Int1, [[ARG5:%.*]] : $*Builtin.Int64):
// CHECK-FORCE-MERGE: [[COND1:%.*]] = builtin "or_Int1"([[ARG1]]{{.*}}, [[ARG2]]
// CHECK-FORCE-MERGE: {{ cond_fail}} [[COND1]]
// CHECK-FORCE-MERGE-NOT: {{ cond_fail}}
// CHECK-FORCE-MERGE: [[LD:%.*]] = load [[ARG5]]
// CHECK-FORCE-MERGE: [[COND2:%.*]] = builtin "or_Int1"([[ARG3]]{{.*}}, [[ARG4]]
// CHECK-FORCE-MERGE: {{ cond_fail}} [[COND2]]
// CHECK-FORCE-MERGE: [[TUPLE:%.*]] = builtin "sadd_with_overflow_Int64"
// CHECK-FORCE-MERGE: [[RES:%.*]] = tuple_extract [[TUPLE]]{{.*}}, 0
// CHECK-FORCE-MERGE: [[OVERFLOW:%.*]] = tuple_extract [[TUPLE]]{{.*}}, 1
// CHECK-FORCE-MERGE: {{ cond_fail}} [[OVERFLOW]]
// CHECK-FORCE-MERGE: return [[RES]]

sil @nonmergeable_cond_fail : $@convention(thin) (Builtin.Int1, Builtin.Int1, Builtin.Int1, Builtin.Int1, @inout Builtin.Int64) -> Builtin.Int64 {
bb0 (%0 : $Builtin.Int1, %1 : $Builtin.Int1, %2 : $Builtin.Int1, %3: $Builtin.Int1, %4 : $*Builtin.Int64):
cond_fail %0 : $Builtin.Int1, "message1"
cond_fail %1 : $Builtin.Int1, "message2"
%5 = load %4 : $*Builtin.Int64
cond_fail %2 : $Builtin.Int1, "message3"
cond_fail %3 : $Builtin.Int1, "message4"
%6 = integer_literal $Builtin.Int1, -1
%7 = integer_literal $Builtin.Int64, 1
%8 = builtin "sadd_with_overflow_Int64"(%5 : $Builtin.Int64, %7 : $Builtin.Int64, %6 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1)
%9 = tuple_extract %8 : $(Builtin.Int64, Builtin.Int1), 0
%10 = tuple_extract %8 : $(Builtin.Int64, Builtin.Int1), 1
cond_fail %10 : $Builtin.Int1, "message5"
return %9 : $Builtin.Int64
}
24 changes: 24 additions & 0 deletions test/embedded/traps-mergeable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// RUN: %target-swift-emit-ir -enable-experimental-feature Extern -enable-experimental-feature Embedded -mergeable-traps -wmo -Xllvm -link-embedded-runtime=0 %s -O | %FileCheck %s

// REQUIRES: swift_in_compiler
// REQUIRES: optimized_stdlib
// REQUIRES: swift_feature_Embedded
// REQUIRES: swift_feature_Extern

@_extern(c)
public func external()

public func test(i: Int, j: Int) {
precondition(i != 0, "precondition 1")
external()
precondition(j != 1, "precondition 2")
}

// CHECK-NOT: call void asm sideeffect ""

// CHECK: define {{.*}}@"$e4main4test1i1jySi_SitF"
// CHECK: tail call void @llvm.trap()
// CHECK: unreachable
// CHECK: tail call void @llvm.trap()
// CHECK: unreachable
// CHECK: }