Skip to content

Hook up -emit-interface-path to a simple AST printer #18224

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

Conversation

jrose-apple
Copy link
Contributor

@jrose-apple jrose-apple commented Jul 25, 2018

Next step after #18090 for module stability / textual interfaces. Still mostly just a dummy at this point, but we can start writing tests.

@jrose-apple jrose-apple requested review from davidungar and CodaFi July 25, 2018 20:31
@jrose-apple
Copy link
Contributor Author

@swift-ci Please test

@jrose-apple
Copy link
Contributor Author

@CodaFi, the second commit is the one relevant to you, since you were looking at filesystem stuff.

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - caafaeee0ecf2d4d9e950e7746c035d6ff4670f5

@jrose-apple
Copy link
Contributor Author

@swift-ci Please test macOS

Copy link
Contributor

@davidungar davidungar left a comment

Choose a reason for hiding this comment

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

Since I'm looking at this without experience, I found somethings that will only confuse newbies, I think. Not sure which thoughts you'll want to take.

@@ -424,6 +424,8 @@ struct PrintOptions {
return result;
}

static PrintOptions printTextualInterfaceFile();

Copy link
Contributor

Choose a reason for hiding this comment

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

Why the blank line here but not after 429?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My understanding is that those two are used by SourceKit in related ways. I could at least add a doc comment to mine, though, to encourage it in the future for the others.

std::error_code moveFileIfDifferent(const llvm::Twine &source,
const llvm::Twine &destination);

/// Invokes \p action with a raw_ostream that refers to a temporary file,
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice comment!

@@ -64,6 +64,21 @@ void PrintOptions::clearSynthesizedExtension() {
TransformContext.reset();
}

PrintOptions PrintOptions::printTextualInterfaceFile() {
Copy link
Contributor

Choose a reason for hiding this comment

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

The name of this function sounds like it does the printing, but it only computes options. I see that it follows the practice of the other two, but still...

@@ -29,6 +35,82 @@ namespace {
};
} // end anonymous namespace

std::error_code swift::atomicallyWritingToFile(
StringRef outputPath, bool binaryMode,
Copy link
Contributor

Choose a reason for hiding this comment

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

Could binaryMode be a const, or is it changed inside of the function?

@@ -29,6 +35,82 @@ namespace {
};
} // end anonymous namespace

std::error_code swift::atomicallyWritingToFile(
StringRef outputPath, bool binaryMode,
llvm::function_ref<void(llvm::raw_pwrite_stream &)> action) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wanted to check the correctness of this function for my review, but it is so long that I doubt my ability to do so.
If you were to break it up into smaller pieces, I could review it with confidence.

/// filesystem errors and ignores empty output paths.
static bool atomicallyWritingToTextFile(
StringRef outputPath, DiagnosticEngine &diags,
llvm::function_ref<bool(llvm::raw_pwrite_stream &)> action) {
if (outputPath.empty())
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

The comment does say that this function ignores empty output paths, but that calling convention does not seem as robust as one that would create an error for an empty output path. But, I don't know the context.

swift::atomicallyWritingToFile(outputPath, /*binary*/false,
[&](llvm::raw_pwrite_stream &out) {
actionFailed = action(out);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it might read a little more clearly with an early return before the if (EC):

if (!actionFailed)
  return false;

Then, you could move the return true; down to the last line of the function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, and have actionFailed default to true? Yeah, that makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, no, that's not quite right. actionFailed and EC are independent.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sigh... You are right. If the action fails (action returns true--BTW action return convention (true == error) ought to also be in the Doxygen, sigh) the file is still written. Is this right? Should the file be removed if action returns true?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think yes, it's a temporary file and it would clutter up the user's directory.

(None of our actions currently fail, so this is kind of a moot point. Another option would be to take that part out.)

Copy link
Contributor

Choose a reason for hiding this comment

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

If we don't remove the file, at least we could include an assertion that the action succeeds with a FIXME about removing the file. Or something...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, wait, hang on. Are you talking about removing the temporary file or the final output file? As written the code always cleans up the temporary file, but does not delete the final output file if there's an error in writing. I figured that was a safer option given Clang's mentioning "you can write to this file but not to the directory that contains it", although it does sometimes mean you've produced erroneous data.

Copy link
Contributor

Choose a reason for hiding this comment

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

If line 326 above evaluates to true, whatever file is written by atomicallyWritingToFile is left on the file system, whichever one that is. That’s what I saw.

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, okay. That's intended but I could see how we might prefer otherwise.

return actionFailed;
}

static bool printAsObjCIfNeeded(StringRef outputPath, ModuleDecl *M,
Copy link
Contributor

Choose a reason for hiding this comment

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

The name says "IfNeeded" but the function has no "if" test in it. I presume that it has to do with an empty output path? Some sort of reformulation could be clearer.

Copy link
Contributor

Choose a reason for hiding this comment

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

Same for printModuleInterfaceIfNeeded below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't change the name of the function, only the implementation. I guess "atomicallyWritingToTextFile" should also have an "IfNeeded" or something?

Copy link
Contributor

Choose a reason for hiding this comment

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

My preference would be for an empty output path to be an error, and to only call the function with a non-empty output path. But one could instead append "IgnoringEmptyOutputPath" to the function name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's definitely too verbose—it makes the call site too hard to read.

I can push the empty string checks back out of these functions, but it was your refactoring of performCompile that put them inside to begin with. :-/

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for asking. It's a question of centralizing the invariants. If there is only one call to printAsObjC, then I would put the test at the call site. If there are two, I would put the test in printAsObjCIfNeeded and have just test and call printAsObjC (or whatever). Same for emitModuleInterface[IfNeeded]. Does that make sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's only one call to all of these (they were all inline in performCompile at one point), so I'll put it outside, then.

Copy link
Contributor

Choose a reason for hiding this comment

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

Great!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, I take it back. There are two calls. Hm. So you're saying it's better to have the test in printAsObjCIfNeeded rather than sink it down to the helper function, since we haven't thought of a way to encode it in the helper function's name without me thinking it's too verbose?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that’s right. One place, so someday we can’t change one and miss the other. Tx

using namespace swift;

bool swift::emitModuleInterface(raw_ostream &out, ModuleDecl *M) {
auto printOptions = PrintOptions::printTextualInterfaceFile();
Copy link
Contributor

Choose a reason for hiding this comment

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

const?

std::error_code EC = swift::atomicallyWritingToFile(outputPath,
/*binary*/true,
action);
if (EC) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a big deal, but slightly better IMO to flip this with an early return if !EC.

@davidungar
Copy link
Contributor

PS: Always a pleasure reviewing your code. Your care inspires me to look for perfection.

///
/// In the latter case, the file at \p source is deleted. If an error occurs,
/// the file at \p source will still be present at \p source.
std::error_code moveFileIfDifferent(const llvm::Twine &source,
Copy link
Contributor

Choose a reason for hiding this comment

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

Yay, more things to upstream!

We'll want more complexity soon, but this is a start.
@jrose-apple jrose-apple force-pushed the a-journey-of-a-thousand-miles branch from caafaee to aacc7ae Compare August 1, 2018 21:34
@jrose-apple
Copy link
Contributor Author

Rebased, feedback incorporated.

@swift-ci Please smoke test

/// Retrieve the set of options suitable for stable textual interfaces.
///
/// This is a format that will be parsed again later, so the output must be
/// consistent and well-formed.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!


fs::file_status status;
(void)fs::status(outputPath, status);
if (fs::exists(status)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably reads slightly better if you flip the "if" and use an early return.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, of course. This is left over from before I pulled it out into its own function. Easy enough.


// Don't use a temporary if the output is a special file. This handles
// things like '-o /dev/null'
if (!fs::is_regular_file(status))
Copy link
Contributor

Choose a reason for hiding this comment

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

If you flip the if above, this turns into return fs::is_regular_file(status)


// Create a temporary file path.
// Insert -%%%%%%%% before the extension (if any), and because some tools
// (noticeable, clang's own GlobalModuleIndex.cpp) glob for build
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo? "noticeably" instead of "noticeable"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess so. It's not my typo; that bit is copied verbatim. They probably meant "notably".

/// If the result is an error, the write won't succeed at all, and the calling
/// operation should bail out early.
static llvm::ErrorOr<bool>
canUseTemporaryForWrite(const StringRef outputPath) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I like that const

// Create a temporary file path.
// Insert -%%%%%%%% before the extension (if any), and because some tools
// (noticeable, clang's own GlobalModuleIndex.cpp) glob for build
// artifacts, also append .tmp.
Copy link
Contributor

Choose a reason for hiding this comment

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

Good comment, but I'm left wondering what would happen as a result of the tool appending .tmp if you didn't add the percents. Is this string just arbitrary? Does anything else count on it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's a mktemp-like pattern, described in the documentation for llvm::sys::fs::createUniqueFile.

tempPath = outputPath.drop_back(outputExtension.size());
tempPath += "-%%%%%%%%";
tempPath += outputExtension;
tempPath += ".tmp";
Copy link
Contributor

Choose a reason for hiding this comment

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

I wish we had Swift's string interpolation, sigh.

This replaces the use of a Clang utility function that was
inexplicably a non-static member function of CompilerInstance. It
would be nice to sink this all the way to LLVM and share the
implementation across both projects, but the Clang implementation does
a handful of things we don't need, and it's hard to justify including
them in an LLVM-level interface. (I stared at
llvm/Support/FileSystem.h for a long time before giving up.)

Anyway, Serialization and FrontendTool both get their atomic writes
now without depending on Clang, and without duplicating the
scaffolding around the Clang API. We should probably adopt this for
all our output files.

No functionality change.
@jrose-apple jrose-apple force-pushed the a-journey-of-a-thousand-miles branch from aacc7ae to 0204561 Compare August 1, 2018 23:41
@jrose-apple
Copy link
Contributor Author

@swift-ci Please smoke test

if (std::error_code error = canUseTemporary.getError())
return error;

Optional<std::string> temporaryPath;
Copy link
Contributor

Choose a reason for hiding this comment

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

I like how the refactoring is going! When I see a declaration, followed by a block that computes it, I wonder if it works better as a separate function. But I'm not sure I see that factoring here would be comprehensible, even though the code wants it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah. It's almost llvm::ErrorOr<Optional<std::string>> but trying to work with that type (and trying to name the function that would be extracted) seems worse than any of the alternatives. tryToOpenTemporaryFile represents the best compromise I came up with.

Copy link
Contributor

@davidungar davidungar left a comment

Choose a reason for hiding this comment

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

This is looking pretty good! There's still a lot going on in swift::atomicallyWritingToFile but I don't see a sensible decomposition.

@jrose-apple jrose-apple merged commit ae04c67 into swiftlang:master Aug 2, 2018
@jrose-apple jrose-apple deleted the a-journey-of-a-thousand-miles branch August 2, 2018 01:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants