Skip to content

Conversation

@tedinski
Copy link
Contributor

Description of changes:

  1. Introduce new rmc-annotations crate. This is necessary because to introduce proc macros, they need to be in a crate by themselves. Added as dependency of rmc crate. (And has to be injected in RMC's rustc flags.)
  2. Added a #[rmc::proof] attribute. In the long run, this is meant to "work" (do the reasonable thing) whether under RMC or not. Under RMC (the only thing we use right now), it just expands to #[rmctool::proof].
  3. The compiler now recognizes that attribute, and records the function and its mangled name in a new .rmc-metadata.json that will likely just grow to subsume everything we emit that isn't the symbol table (e.g. .type_map.json) later on.

Resolved issues:

Resolves... figure that out

Call-outs:

  1. We should merge Add option to restrict vtable function pointers #536 before this, as I think there will be git merge conflicts and this is the smaller changer, easier to fix.
  2. Right now functions marked with #[rmc::proof] must be pub or we silently don't notice it at all. I need to figure out a fix for this. Ideally it'd just work without issue, but I'd even settle for an error check for a missing pub if that's all we can figure out how to do, but even that's not obvious to me since we never call codegen_function as that's the problem in the first place!
  3. "Actually using this emitted data" is a future PR, where we start implementing a production-quality cargo rmc. Notably, though, cargo typically produces a single .rmc-metadata.json (and friends) per-crate. So we'd really just be looking for the proof harnesses in our crate, not from our dependencies. (Or perhaps some similar heuristic. Only from local crates?)

Testing:

  • How is this change tested? TODO - draft PR

  • Is this a refactor change?

Checklist

  • Each commit message has a non-empty body, explaining why the change was made
  • Methods or procedures are documented
  • Regression or unit tests are included, or existing tests cover the modified code
  • My PR is restricted to a single feature or bugfix

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

I'm so happy to see this going through! I know this is just a draft, but I left a few comments. Cheers

}

pub fn filename(&self) -> Option<String> {
match self {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a suggestion. This could be a one liner:

if let Location::Loc { file, .. } = self { Some(file.to_string) } else { None }

///
/// Currently, this is only proof harness annotations.
/// i.e. `#[rmc::proof]` (which rmc-annotations translates to `#[rmctool::proof]` for us to handle here)
fn codegen_rmctool_attributes(&mut self) {
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. I would suggest renaming to something like collect_rmc_attributes? This is not quite codegen.
  2. I was also wondering if these functions should be inside metadata.rs instead.
  3. Why do we need &mut?

fn codegen_rmctool_attributes(&mut self) {
let instance = self.current_fn().instance();

for attr in self.tcx.get_attrs(instance.def_id()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please add a debug statement?

let instance = self.current_fn().instance();

for attr in self.tcx.get_attrs(instance.def_id()) {
if matches_rmctool_attr(attr, "proof") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a sanity check somewhere that validates the rmctool attributes used in the function?

}
}

fn matches_rmctool_attr(attr: &ast::Attribute, name: &str) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would return a set of rmctool attributes. We are planning to add multiple attributes, right? I don't think this method will scale well.

// but outside RMC, this code is likely never called.
let mut result = TokenStream::new();

result.extend("#[allow(dead_code)]".parse::<TokenStream>().unwrap());
Copy link
Contributor

Choose a reason for hiding this comment

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

I wouldn't add this. We don't want proof methods to inadvertently make it to the final library / binary. We can either extend with a #[cfg(proof)] or just document that users should do that.

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 thought about additionally branching into having a debug/release version of the macro, with the release removing the function entirely. Would that work for you?

My goal here was to ensure #[proof] is enough all by itself and nothing further needs to be added, just like #[test]

But come to think of it, I think this won't work as-is anyway. If the goal is to let users add rmc as a dev-dependency, then this package (defining the #[proof] macro) wouldn't be in the dependency list anyway during a normal non-test compile. :( Might have to have a rethink about that problem...

Copy link
Contributor

Choose a reason for hiding this comment

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

Gotcha. I'm OK with the idea of users adding #[proof] by itself. I would just prefer for it to be a no-op if they are not using rmc. I.e., I would prefer if we remove the #[allow(dead_code)].

pub fn proof(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut result = TokenStream::new();

result.extend("#[rmctool::proof]".parse::<TokenStream>().unwrap());
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please add the #[no_mangle] for now? You can link to #661 for us to remove 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.

I thought the need for no_mangle was just the UI problem of predicting the mangled name. This feature is intended to solve that, since we get a map:

{
      "pretty_name": "testing",
      "mangled_name": "_RNvCs1NZMV7qnTwy_1f7testing",
      "original_file": "/home/ubuntu/experiments/proof_anno/f.rs"
}

Is there some other reason for no_mangle? Otherwise, I don't know why I'd add it here.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was thinking about addressing that in a follow up PR so this PR doesn't get too big and we can merge it sooner rather than later. If you do add that to this PR, then it's OK to keep it without the no_mangle.

There are two reasons why we rely on the #[no_mangle] today. One is the friendly name and the second is that no_mangle makes the function public.

@tedinski
Copy link
Contributor Author

tedinski commented Dec 2, 2021

  1. Merged and resolved conflicts with Alexa's changes. (Went easier than expected!)
  2. I've renamed the crate to rmc_macros as that's more literal (and probably more correct, since eventually I think we might end up with a rmc::stub! { ... } macro...)
  3. I've added no_mangle. The change we've made in Re-enable RMC to allow users to verify non-public main. #656 (I think) meant making a proof harness pub was no longer sufficient to codegen it, so I had to do something. I'm still not happy with this choice, since it comes with significant drawbacks (like name collisions).
  4. I've broken the proof macro into three cases, instead of the old two, based on cfg(test):
    1. !RMC, !Test (i.e. "normal build"): eliminate the proof harness. This will probably never happen since we expect #[rmc::proof] to (eventually) be brought in via a dev-dependency, but this ensures that's the case.
    2. !RMC, Test: Leave it, but suppress dead code warnings. This makes it editable in IDEs and ensures it get compiled under normal testing conditions.
    3. RMC: Now we know we can use the tool attribute. Also, as a hack temporarily, no_mangle so the function actually gets codegen'd.

Remaining things before mergable:

  1. Revisit naming/comments around codegen_rmctool_attributes. Celina thought the name was weird and didn't notice the self.proof_harnesses.push mutation, which means the code isn't obvious enough yet. So I should fix that.
  2. Add line numbers as requested by Zyad. Good idea. I had them originally but lost them in some debugging shuffling. (This code had a lot of iterations and failed experiments.)
  3. I really want a better fix so I don't need no_mangle :(

@celinval
Copy link
Contributor

celinval commented Dec 2, 2021

Regarding # 3, I believe it should still work if you export RUSTFLAGS="--crate-type=lib". It should also work for cargo projects that are libraries.

Maybe we should add an option to rmc to allow switching them or just detect whether user target function is main or not.

@tedinski
Copy link
Contributor Author

tedinski commented Dec 6, 2021

I think this is ready to merge.

  1. Hopefully the code Celina tripped over is much clearer now.
  2. I haven't added a requested debug line (not sure what'd be useful here...)

Checklist for me after merge:

My next steps after this are to get started on a "real" Rust cargo-rmc application, which will subsume our current scripts and be the tool that actually consumes rmc-metadata from this PR.

@tedinski tedinski marked this pull request as ready for review December 6, 2021 21:45
@tedinski tedinski requested a review from a team as a code owner December 6, 2021 21:45
@celinval
Copy link
Contributor

celinval commented Dec 7, 2021

I think this is ready to merge.

1. Hopefully the code Celina tripped over is much clearer now.

2. I haven't added a requested debug line (not sure what'd be useful here...)

Checklist for me after merge:

* [ ]  Update [RMC should use fully qualified name for harness selection and it should not rely on `no_mangle` attribute #661](https://github.com/model-checking/rmc/issues/661) to add more information on what the problem is and the path forward to eliminating `no_mangle`

* [ ]  Create feature request for `#[proof(unwind = 9)]` optional unwind annotations

* [ ]  Create feature request for annotation validation (e.g. right now, maybe just objecting to duplicate proof annotations? Or if the proof harness has any parameters? Support for parameters is something we could add later...)

* [ ]  Create feature request to merge `type_map` and `restrictions` json files into `rmc-metadata`

* [ ]  Create an issue to ensure `cargo-rmc` includes tests that exercise testing the output in `rmc-metadata`. Currently, we're not testing the changes in this PR.

My next steps after this are to get started on a "real" Rust cargo-rmc application, which will subsume our current scripts and be the tool that actually consumes rmc-metadata from this PR.

For the second bullet, we already have: #600

Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

Just a few minor comments. Thanks

}

/// Update `self` (the goto context) to add the current function as a listed proof harness
fn handle_rmctool_proof(&mut self) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the plan for proof options like unwind? Are you thinking about adding arguments to this attribute or creating new attributes?

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'm thinking add arguments to this attribute, yes.

None
};

let metadata = RmcMetadata { proof_harnesses: c.proof_harnesses };
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need this?

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'm not sure what you mean by 'this'. The RmcMetadata type?) I'm planning on the rmc-metadata file becoming where we emit everything that isn't the symtab. So type_map, function restrictions, and more things like notable/important warnings (instead of just logging them).

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I mean the copy statement.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Still not sure what the "copy statement" is.

@celinval
Copy link
Contributor

celinval commented Dec 7, 2021

I also forgot... can you please a testcase?

@tedinski
Copy link
Contributor Author

tedinski commented Dec 8, 2021

I also forgot... can you please a testcase?

I really want to but nothing reads this yet, so I was going to create an issue to not forget, and include that as part of cargo-rmc.

@celinval
Copy link
Contributor

celinval commented Dec 8, 2021

I also forgot... can you please a testcase?

I really want to but nothing reads this yet, so I was going to create an issue to not forget, and include that as part of cargo-rmc.

For now, it would at least ensure that things compile and that this code doesn't crash.

@tedinski
Copy link
Contributor Author

tedinski commented Dec 8, 2021

For now, it would at least ensure that things compile and that this code doesn't crash.

Ah, this code is running by default, so all existing tests are exercising it. We're just not examining the output to see if it's correct (i.e. that proof harnesses show up and only the correct ones, etc)

@tedinski
Copy link
Contributor Author

tedinski commented Dec 8, 2021

For now, it would at least ensure that things compile and that this code doesn't crash.

Oh wait, you mean add a test that has a harness to that actual branch gets run, even if nothing looks at it. I can do that.

@tedinski
Copy link
Contributor Author

tedinski commented Dec 8, 2021

Ok, I've added a cargo-rmc test with a proof annotation, so now the code for the macro and the branch that actually adds a proof harness to the rmc-metadata is actually getting run in our tests. (With nothing checking on the output yet to ensure it's correct, but at least running and not crashing.)

@tedinski tedinski merged commit 5b9071e into model-checking:main Dec 10, 2021
@tedinski tedinski deleted the proof-anno branch December 10, 2021 17:01
@jaisnan jaisnan self-assigned this Jan 5, 2022
@jaisnan jaisnan removed their assignment Jan 5, 2022
tedinski added a commit to tedinski/rmc that referenced this pull request Apr 26, 2022
* Introduce rmc::proof function attribute

* add no_mangle as temporary measure to force function codegen
tedinski added a commit that referenced this pull request Apr 27, 2022
* Introduce rmc::proof function attribute

* add no_mangle as temporary measure to force function codegen
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