Page MenuHomePhabricator

[clang][slh] add Clang attr no_speculative_load_hardening
ClosedPublic

Authored by zbrid on Nov 26 2018, 11:16 AM.

Details

Summary

This attribute will allow users to opt specific functions out of
speculative load hardening. This compliments the Clang attribute
named speculative_load_hardening. When this attribute or the attribute
speculative_load_hardening is used in combination with the flags
-mno-speculative-load-hardening or -mspeculative-load-hardening,
the function level attribute will override the default during LLVM IR
generation. For example, in the case, where the flag opposes the
function attribute, the function attribute will take precendence.
The sticky inlining behavior of the speculative_load_hardening attribute
may cause a function with the no_speculative_load_hardening attribute
to be tagged with the speculative_load_hardening tag in
subsequent compiler phases which is desired behavior since the
speculative_load_hardening LLVM attribute is designed to be maximally
conservative.

Diff Detail

Repository
rC Clang

Event Timeline

zbrid created this revision.Nov 26 2018, 11:16 AM
zbrid abandoned this revision.Nov 26 2018, 11:19 AM

I need to work on this some more.

zbrid updated this revision to Diff 175599.Nov 27 2018, 4:29 PM

Add tests and documentation

kristof.beyls added inline comments.Dec 3 2018, 10:33 AM
clang/include/clang/Basic/AttrDocs.td
3667 ↗(On Diff #175599)

Hi Zola,

I'm afraid I don't have time for an in-depth look in the next few days,
but I wonder if you have an answer to the follow higher-level question.
I wonder why not define no_speculative_load_hardening as giving a
guarantee that speculative load hardening doesn't happen on the code.
I'm not sure if there are users of this attribute that need the
guarantee that speculative load hardening doesn't happen, but there may?
I guess that to implement such behaviour, it would require also introducing a
no_speculative_load_hardening attribute at the LLVM-IR level?
Or somehow (I don't know if that's possible) make the existing
speculative_load_hardening attribute at LLVM-IR level have 3 potential
states: "no hardening", "must harden" and "don't care".
I guess the current proposed behaviour is because that's easier to implement,
or is there another reason?
What do you think?

Thanks,

Kristof

chandlerc added inline comments.Dec 4 2018, 1:53 AM
clang/include/clang/Basic/AttrDocs.td
3663 ↗(On Diff #175599)

Maybe say "is not needed for the function body" to make it more clear?

3667 ↗(On Diff #175599)

FWIW, I had suggested this design because I think it is simple, and addresses the only use case I have in mind.

That use case is essentially to remove SLH from functions that are performance sensitive and manually mitigated (if at risk at all). The intent is that people can use this based on profiling to recover some performance hit from SLH where it is safe to do so. As a consequence, the goal was not what you describe.

My thought process goes like this:

  • Some routines may explicitly opt into SLH because they are at unusual risk. There is an attribute to do this. This attribute always wins: the code with the attribute is *never* allowed to be emitted w/o the hardening.
  • Other routines may opt *out* of SLH if it is enabled on the command line, much like routines may opt out of sanitizer instrumentation.
  • Everything else picks up whichever default is selected at the command line.

Now, we have to deal with what happens if the inliner tries to inline a function marked with an explicit attribute into code marked with this no_speculative_load_hardening attribute. I don't expect this to ever realistically occur, but we need to handle it if it does. Sadly, we can't just produce an error -- the backend may be the only thing that sees this. We could add an LLVM attribute as you suggest and block inlining in this case, but that seems very intrusive and is only designed to handle an edge case that we don't anticipate happens much if ever in practice. The final option which seems the most reasonable is what this implements: the positive attribute always wins. IMO, this is a reasonable handling of the edge case, and keeps things as simple as possible.

aaron.ballman added inline comments.Dec 4 2018, 9:43 AM
clang/include/clang/Basic/AttrDocs.td
3640 ↗(On Diff #175599)

Add backticks around the command line flag. Bonus points if you can link it to the command line documentation for that command.

3663 ↗(On Diff #175599)

Also, use RST formatting for NOT rather than capitalizing it. (Either surround it like *this* for italics or like this for bold.)

3667 ↗(On Diff #175599)

I think the current design is the right approach, but I'd appreciate seeing a code example in the documentation that more clearly shows the expectation. Something like:

__attribute__((speculative_load_hardening)) int foo(int i) { return i; }
__attribute__((no_speculative_load_hardening)) int bar(int i) {
  // Note: bar() may still have speculative load hardening if foo() is
  // inlined into bar(). Mark foo() with __attribute__((noinline)) to
  // avoid this situation.
  return foo(i);
}

Feel free to devise a less terrible example, too. ;-)

One question I have is: do we think it's worth diagnosing this slightly different case, given that the user has signaled explicitly that they want foo() inlined, but the call is has speculative load hardening disabled:

__attribute__((speculative_load_hardening)) inline int foo(int i) { return i; }
__attribute__((no_speculative_load_hardening)) int bar(int i) {
  return foo(i); // Warning?
}
kristof.beyls added inline comments.Dec 5 2018, 10:02 AM
clang/include/clang/Basic/AttrDocs.td
3667 ↗(On Diff #175599)

Thanks for explaining the detailed rationale.

Referring to: "Now, we have to deal with what happens if the inliner tries to inline a function marked with an explicit attribute into code marked with this no_speculative_load_hardening attribute." , which is the case of "SLH-function ---inline into---> noSLH-function".
I'm assuming you don't call out the reverse "noSLH-function ---inline into---> SLH-function" situation as you can mark the noSLH-function with a noinline attribute if you really want to prevent SLH to be applied to the code in the noSLH-function.

Overall, I agree that it doesn't seem worthwhile to introduce the extra complexity needed for that remaining "SLH-function ---inline into----> noSLH-function" theoretical edge case.
Or in other words, you've convinced me the design tradeoffs in this patch are right.

zbrid updated this revision to Diff 177914.Dec 12 2018, 2:05 PM

Update documentation based on review comments.

zbrid updated this revision to Diff 177917.Dec 12 2018, 2:15 PM

Fix indentation of code block in attribute documentation for no speculative load hardening

aaron.ballman added inline comments.Dec 14 2018, 5:07 AM
clang/include/clang/Basic/AttrDocs.td
3786 ↗(On Diff #177917)

I think this is meant to be .. code-block:: c ?

clang/test/SemaCXX/attr-no-speculative-load-hardening.cpp
5 ↗(On Diff #177917)

Should this situation be diagnosed?

__attribute__((no_speculative_load_hardening))
void func();

__attribute__((speculative_load_hardening))
void func();

Which attribute should "win"?

If you want to diagnose, you can use checkAttrMutualExclusion() to handle this in SemaDeclAttr.cpp.

zbrid updated this revision to Diff 178526.Dec 17 2018, 1:50 PM

detect (some, not yet all) incompatible uses of attrs; move tests to correct folders

zbrid marked 8 inline comments as done.Dec 17 2018, 1:58 PM
zbrid added inline comments.
clang/include/clang/Basic/AttrDocs.td
3667 ↗(On Diff #175599)

@aaron.ballman I will try to implement a warning like you proposed for that particular case. Still looking into it., but wanted to ack.

clang/test/SemaCXX/attr-speculative-load-hardening.cpp
19 ↗(On Diff #178526)

Should this situation be diagnosed?

attribute((no_speculative_load_hardening))
void func();

attribute((speculative_load_hardening))
void func();
Which attribute should "win"?

If you want to diagnose, you can use checkAttrMutualExclusion() to handle this in SemaDeclAttr.cpp.

@aaron.ballman
That's a great idea. So far I've added the use of checkAttrMutualExclusion which catches the cases in this test and the three added below. I'm working on a change that will catch the case in your example. I think I'll have to make a change in Clang to check for mutual exclusion when the attributes are merged rather than where they are currently checked. I don't have all the details yet, but I think I'll send a separate patch for that once I figure out what's going on.

zbrid marked an inline comment as done.Dec 17 2018, 2:05 PM
zbrid added inline comments.
clang/test/SemaCXX/attr-speculative-load-hardening.cpp
19 ↗(On Diff #178526)

After looking more closely at this, I think I only need to write a function for this particular attribute to be merged differently than the default way which will cause the diagnostic if necessary in this case which is a smaller change than I thought, so I'll add the change to this patch.

zbrid updated this revision to Diff 178544.Dec 17 2018, 3:30 PM

Give an error when a function was both attributes specified at different places in the code.

zbrid updated this revision to Diff 178545.Dec 17 2018, 3:34 PM
zbrid marked an inline comment as done.

rebase

aaron.ballman accepted this revision.Dec 18 2018, 6:49 AM

LGTM aside from a minor formatting question.

clang/lib/Sema/SemaDeclAttr.cpp
4135–4136 ↗(On Diff #178545)

Is this the formatting you get from clang-format?

This revision is now accepted and ready to land.Dec 18 2018, 6:49 AM
zbrid updated this revision to Diff 178738.Dec 18 2018, 11:08 AM

clang format

zbrid updated this revision to Diff 178739.Dec 18 2018, 11:12 AM

more clang format

zbrid updated this revision to Diff 180575.Mon, Jan 7, 3:15 PM

[slh] add clang diagnostic

zbrid marked an inline comment as done.Mon, Jan 7, 3:16 PM
zbrid added inline comments.
clang/lib/Sema/SemaDecl.cpp
13079 ↗(On Diff #180575)

@aaron.ballman Could you take a look at this clang diagnostic I added for the case you suggested in another comment?

aaron.ballman added inline comments.Tue, Jan 8, 5:50 AM
clang/include/clang/Sema/Sema.h
1718–1719 ↗(On Diff #180575)

You should give these parameters identifiers to help people reading the source code understand what should be passed for each argument.

clang/lib/Sema/SemaDecl.cpp
13080–13081 ↗(On Diff #180575)

The identifiers don't match the usual naming conventions (here and elsewhere).

13082 ↗(On Diff #180575)

I would turn this into an early return rather than indent the entire function body one level for it.

13087–13089 ↗(On Diff #180575)

llvm::copy(Node->children(), std::back_inserter(Nodes));

13090 ↗(On Diff #180575)

Should be const auto * (same below) rather than inferring the const qualification.

Moreover, this seems incorrect -- you don't mean any declref to an attributed function, do you? I would have imagined that only call expressions matter. e.g., this should not diagnose:

[[clang::speculative_load_hardening]] inline int bar(int x) { return x; }

[[clang::no_speculative_load_hardening]] int foo() {
  if (bar) {  // No reason to warn about speculative load hardening because there's no call.
  }
  return sizeof(bar(0)); // Unevaluated expression probably doesn't need a warning either.
}

I'm a bit concerned about the performance cost of touching every child AST node in the function body. I wonder if it would be possible to check CallExprs as they're handled instead? I'm not certain if there's a way to get from the Expr to the context in which the expression appears so that we can see if the expression is within a function with speculative load hardening disabled or not, so this may be a dead-end idea.

If it turns out that handling it at the CallExpr site isn't feasible, then I think we should probably revert this diagnostic and go with the previous version of the patch. We can take another swing at a diagnostic if we find that people actually hit this corner case often enough to warrant the extra expense of the check (or we can solve it another way, through clang-tidy or the static analyzer). WDYT?

13096 ↗(On Diff #180575)

You should pass in ND and Callee directly rather than calling getName() on them; the diagnostic engine will handle properly quoting the name that's output as well.

13129 ↗(On Diff #180575)

Why are you qualifying the call?

13294 ↗(On Diff #180575)

Spurious change?

zbrid marked 3 inline comments as done.Tue, Jan 8, 10:12 AM
zbrid added inline comments.
clang/lib/Sema/SemaDecl.cpp
13090 ↗(On Diff #180575)

I looked into handling this at the CallExpr site and it looks like the context needed for this diagnostic is available. I'll update the patch when I get that version working. That suggestion makes a lot of sense to me.

As for checking every DeclRefExpr, that was due to my misunderstanding of the AST. Given what you said, I think what I really want are the DeclRefExpr that are children of CallExpr. That'll be reflected in the updated patch.

I'll keep all these style comments in mind when making the update too.

13294 ↗(On Diff #180575)

Yes, I'll revert that.

zbrid updated this revision to Diff 182059.Wed, Jan 16, 8:30 AM

[clang][slh] remove diagnostic for now

zbrid updated this revision to Diff 182060.Wed, Jan 16, 8:34 AM

[clang][slh] delete stray includes from removed code

zbrid added a comment.Wed, Jan 16, 8:35 AM

I'm going to revert the diagnostic changes for now and commit this patch. I tried a few ways to implement this without success so far. I have one additional way I want to try based on some advice on the cfe-dev list, but I'll submit it in another patch once I get some time to try and implement it.

I'm going to revert the diagnostic changes for now and commit this patch. I tried a few ways to implement this without success so far. I have one additional way I want to try based on some advice on the cfe-dev list, but I'll submit it in another patch once I get some time to try and implement it.

That sounds reasonable to me -- I didn't intend for it to hold your patch up; it can always be done separately.

This revision was automatically updated to reflect the committed changes.