diff --git a/Package.swift b/Package.swift index 124672571..04ee3d89c 100644 --- a/Package.swift +++ b/Package.swift @@ -47,6 +47,10 @@ let package = Package( // See the "Dependencies" section below. ], targets: [ + .target( + name: "_InstructionCounter" + ), + .target( name: "SwiftFormat", dependencies: [ @@ -108,6 +112,7 @@ let package = Package( .executableTarget( name: "swift-format", dependencies: [ + "_InstructionCounter", "SwiftFormat", .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SwiftSyntax", package: "swift-syntax"), diff --git a/Sources/_InstructionCounter/include/InstructionsExecuted.h b/Sources/_InstructionCounter/include/InstructionsExecuted.h new file mode 100644 index 000000000..af9d9a415 --- /dev/null +++ b/Sources/_InstructionCounter/include/InstructionsExecuted.h @@ -0,0 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 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 +// +//===----------------------------------------------------------------------===// + +#include + +/// On macOS returns the number of instructions the process has executed since +/// it was launched, on all other platforms returns 0. +uint64_t getInstructionsExecuted(); diff --git a/Sources/_InstructionCounter/src/InstructionsExecuted.c b/Sources/_InstructionCounter/src/InstructionsExecuted.c new file mode 100644 index 000000000..4df6fa632 --- /dev/null +++ b/Sources/_InstructionCounter/src/InstructionsExecuted.c @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 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 +// +//===----------------------------------------------------------------------===// + +#if __APPLE__ +#include +#if TARGET_OS_MAC && !TARGET_OS_IPHONE +#define TARGET_IS_MACOS 1 +#endif +#endif + +#include "InstructionsExecuted.h" + +#ifdef TARGET_IS_MACOS +#include +#include +#include + +uint64_t getInstructionsExecuted() { + struct rusage_info_v4 ru; + if (proc_pid_rusage(getpid(), RUSAGE_INFO_V4, (rusage_info_t *)&ru) == 0) { + return ru.ri_instructions; + } + return 0; +} +#else +uint64_t getInstructionsExecuted() { + return 0; +} +#endif diff --git a/Sources/swift-format/Subcommands/Format.swift b/Sources/swift-format/Subcommands/Format.swift index 1a7f89d23..7a0378612 100644 --- a/Sources/swift-format/Subcommands/Format.swift +++ b/Sources/swift-format/Subcommands/Format.swift @@ -30,6 +30,9 @@ extension SwiftFormatCommand { @OptionGroup() var formatOptions: LintFormatOptions + @OptionGroup(visibility: .hidden) + var performanceMeasurementOptions: PerformanceMeasurementsOptions + func validate() throws { if inPlace && formatOptions.paths.isEmpty { throw ValidationError("'--in-place' is only valid when formatting files") @@ -37,9 +40,11 @@ extension SwiftFormatCommand { } func run() throws { - let frontend = FormatFrontend(lintFormatOptions: formatOptions, inPlace: inPlace) - frontend.run() - if frontend.diagnosticsEngine.hasErrors { throw ExitCode.failure } + try performanceMeasurementOptions.countingInstructionsIfRequested { + let frontend = FormatFrontend(lintFormatOptions: formatOptions, inPlace: inPlace) + frontend.run() + if frontend.diagnosticsEngine.hasErrors { throw ExitCode.failure } + } } } } diff --git a/Sources/swift-format/Subcommands/Lint.swift b/Sources/swift-format/Subcommands/Lint.swift index 46f28f7c3..985100647 100644 --- a/Sources/swift-format/Subcommands/Lint.swift +++ b/Sources/swift-format/Subcommands/Lint.swift @@ -28,12 +28,17 @@ extension SwiftFormatCommand { ) var strict: Bool = false - func run() throws { - let frontend = LintFrontend(lintFormatOptions: lintOptions) - frontend.run() + @OptionGroup(visibility: .hidden) + var performanceMeasurementOptions: PerformanceMeasurementsOptions - if frontend.diagnosticsEngine.hasErrors || strict && frontend.diagnosticsEngine.hasWarnings { - throw ExitCode.failure + func run() throws { + try performanceMeasurementOptions.countingInstructionsIfRequested { + let frontend = LintFrontend(lintFormatOptions: lintOptions) + frontend.run() + + if frontend.diagnosticsEngine.hasErrors || strict && frontend.diagnosticsEngine.hasWarnings { + throw ExitCode.failure + } } } } diff --git a/Sources/swift-format/Subcommands/PerformanceMeasurement.swift b/Sources/swift-format/Subcommands/PerformanceMeasurement.swift new file mode 100644 index 000000000..388009a93 --- /dev/null +++ b/Sources/swift-format/Subcommands/PerformanceMeasurement.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 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 ArgumentParser +import _InstructionCounter + +struct PerformanceMeasurementsOptions: ParsableArguments { + @Flag(help: "Measure number of instructions executed by swift-format") + var measureInstructions = false + + /// If `measureInstructions` is set, execute `body` and print the number of instructions + /// executed by it. Otherwise, just execute `body` + func printingInstructionCountIfRequested(_ body: () throws -> T) rethrows -> T { + if !measureInstructions { + return try body() + } else { + let startInstructions = getInstructionsExecuted() + defer { + print("Instructions executed: \(getInstructionsExecuted() - startInstructions)") + } + return try body() + } + } +}