Skip to content
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

Use core and alloc crates for no_std compatibility #1156

Merged
merged 19 commits into from
Sep 15, 2021
Merged

Conversation

soareschen
Copy link
Contributor

@soareschen soareschen commented Jul 7, 2021

Partially Closes: #1158

Description

In this PR I search for the use of std:: imports and change them to use core or alloc. The unsupported constructs are left to be imported from std and be fixed through other PRs for #1158.

A notable change in this PR is that we are now using BTreeMap instead of HashMap, and BTreeSet instead of HashSet. The reason is that the formers are not supported in the alloc crate, as they depends on random sources which are not available in no-std. I'm not sure if this change would break anything, so we might want to investigate further on this.


For contributor use:

  • Updated the Unreleased section of CHANGELOG.md with the issue.
  • If applicable: Unit tests written, added test to CI.
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
  • Updated relevant documentation (docs/) and code comments.
  • Re-reviewed Files changed in the Github PR explorer.

@soareschen soareschen mentioned this pull request Jul 7, 2021
25 tasks
@soareschen soareschen marked this pull request as ready for review July 7, 2021 12:30
@romac
Copy link
Member

romac commented Jul 7, 2021

What's the advantage of doing this rather than use something like no-std-compat?

@soareschen
Copy link
Contributor Author

What's the advantage of doing this rather than use something like no-std-compat?

  • core and alloc are officially maintained by Rust itself. So we can be confident that they will be supported in the long run.
  • The author of no-std-compat has sadly passed away, and the library is likely no longer maintained.
  • Std wrapper crates tend to get outdated quickly when new versions of the standard library is released. I have experienced similar issue in Haskell, where the base-noprelude package which wraps base got outdated when new version of GHC is released, and that caused a lot of pain to update / workaround all dependents to that wrapper package.
  • Polyfill std libraries do not necessarily follow the exact behavior of the original std constructs. This means we cannot easily refer back to the vanilla Rust std documentation. On the other hand core and alloc guarantee the same implementation as in std, and drops the unsupported constructs otherwise.
  • If we are using constructs unsupported by core and alloc such as std::io, it is likely that any polyfill crate would have custom implementation of the constructs. In such case we should use the polyfill crate directly whether in std or no-std mode, so that there is less maintenance burden of switching between two versions of the code.
    • The polyfill crate can still switch between std mode and polyfill mode behind the scene. But from the perspective of ibc-rs as the user of the library, that should be transparent.
    • Using the polyfill crate directly means that we are aware of its existence and always refer to the polyfill's documentation instead of the official Rust documentation. So in practice it is the same as us using a third party library instead of std for that functionality.

@romac
Copy link
Member

romac commented Jul 8, 2021

Very good points, thanks @soareschen! Let's go with that then!

Some follow-up questions:

a) Can also add a workflow to the CI to build the ibc crate in no-std mode, to ensure we don't accidentally introduce dependencies on std and friends?
b) I am wondering why we are doing this changes in the ibc-relayer, ibc-relayer-cli, ibc-telemetry and ibc-proto-compiler crates when those crates are not meant to be compiled with no-std?
c) Were the changes in the ibc-proto crate done manually? If so, can we automate that somehow? eg. via a prost flag/feature or within the proto-compiler crate as a post-processing step?

@soareschen
Copy link
Contributor Author

a) Can also add a workflow to the CI to build the ibc crate in no-std mode, to ensure we don't accidentally introduce dependencies on std and friends?

The current change do not support no-std just yet. There are still use of constructs from std such as std::error::Error. So we will need further PRs to fix that. The purpose of this PR is so that we can minimize the later changes required when no-std support is ready.

b) I am wondering why we are doing this changes in the ibc-relayer, ibc-relayer-cli, ibc-telemetry and ibc-proto-compiler crates when those crates are not meant to be compiled with no-std?

I think it is good to be consistent with the convention so that we do not need to switch between the conventions when developing on different crates. The use of core and alloc also makes it more clear of which features we are using are only supported in std, in case we want to support no-std in those crates later on.

c) Were the changes in the ibc-proto crate done manually? If so, can we automate that somehow? eg. via a prost flag/feature or within the proto-compiler crate as a post-processing step?

That's a good point! I have reverted the changes and created #1164 to address the issue separately.

@romac romac added the E: no-std External: support for no_std compatibility label Jul 21, 2021
@romac romac changed the title Use core and alloc crates for no_std compatibility Use core and alloc crates for no_std compatibility Jul 28, 2021
@romac
Copy link
Member

romac commented Aug 10, 2021

@soareschen What do you think about defining our own prelude which re-exports commonly used types such as Vec, String, FromStr, ToString, etc?

@soareschen
Copy link
Contributor Author

What do you think about defining our own prelude which re-exports commonly used types such as Vec, String, FromStr, ToString, etc?

I can see that having such prelude would make it more convenient to use common constructs. However I would advise against building our own prelude library, as it can be tedious to keep the exports in sync with a proper "subset" of std. When there are inconsistencies, it can also cause confusion when developers are referring to the official Rust documentation.

What we could do instead is to create a minimal prelude that re-exports everything from core::prelude::v1 and alloc::prelude::v1. That way we are still delegating the management of what is being exported to core and alloc.

Note that the alloc prelude is currently nightly only. So I think it is fine to have custom re-exports for now that mirror what is available in alloc::prelude::v1. Looking at the definitions, it also seems like core::prelude::v1 is already made available in no_std. So the constructs from alloc::prelude::v1 are probably the only ones we need to add on top of it.

@romac
Copy link
Member

romac commented Aug 10, 2021

What we could do instead is to create a minimal prelude that re-exports everything from core::prelude::v1 and alloc::prelude::v1. That way we are still delegating the management of what is being exported to core and alloc.

Note that the alloc prelude is currently nightly only. So I think it is fine to have custom re-exports for now that mirror what is available in alloc::prelude::v1. Looking at the definitions, it also seems like core::prelude::v1 is already made available in no_std. So the constructs from alloc::prelude::v1 are probably the only ones we need to add on top of it.

Sounds good to me!

@adizere
Copy link
Member

adizere commented Aug 12, 2021

What is the status of this PR? I think we should move forward with it.

Are there any implications of using BTreeMap and BTreeSet, performance, or correctness, that we should be aware of?

@romac
Copy link
Member

romac commented Aug 12, 2021

Are there any implications of using BTreeMap and BTreeSet, performance, or correctness, that we should be aware of?

I don't see any implications on correctness as I don't believe we every relied on the iteration order of elements of a HashMap or HashSet (which would be dumb anyway as it is non-deterministic).

As for performance, most operations on BTree-based data-structures have worse asymptotic complexity than their Hash-based counterparts, see the table below.

I don't believe that's an issue at the moment, as we do not maintain any data structure large enough for it to matter (especially as most operations in Hermes are IO-bound anyway).

Screen Shot 2021-08-12 at 13 04 21
source

modules/src/handler.rs Show resolved Hide resolved
@soareschen
Copy link
Contributor Author

I have made a few more changes to this PR after merging with master:

#![no_std] with extern crate std

I have now activated #![no_std] in the ibc crate, with an extern crate std to explicitly re-import the std crate. This in effect remove the implicit imports from std::prelude, so that we do not accidentally use features that are implicitly exported by std prelude, such as println!.

Custom crate::prelude Crate

With #![no_std] activated, this means that std prelude constructs such as format!, vec!, String, and Vec are no longer implicitly imported in every Rust module. To compensate with this, I have added a private prelude crate that can be imported with use crate::prelude::* inside the ibc crate. The prelude essentially re-exports everything from core::prelude::v1 and alloc::prelude::v1, but excludes std-only prelude constructs such as println!.

Unfortunately, it seems like Rust still do not allow us to set a custom prelude inside a crate. (rust-lang/rfcs#890) So it is necessary to call use crate::prelude::* in every individual modules that need to use prelude constructs. Nevertheless, this should hopefully make the developers' life easier as compared to having to import from modules such as alloc::format, alloc::string::String, etc everywhere.

Remaining std Uses

With the two changes above, there are only a few places remaining in the ibc crate that still uses std:

  • std::error::Error - This use can be removed once we upgrade to new version of tendermint-rs.
  • std::thread::sleep and std::printlnt - These are only used in tests, so we can probably conditionally allow std in #[cfg(test)].
  • std::time::SystemTime - This seems to be the only tricky bit left that is being used by the protobuf code generated by prost/tonic. We may want to investigate whether it is possible to generate no-std-compliant structs from prost that do not use SystemTime.

@romac
Copy link
Member

romac commented Aug 25, 2021

Thanks for the update @soareschen! This all sounds good to me.

std::time::SystemTime - This seems to be the only tricky bit left that is being used by the protobuf code generated by prost/tonic. We may want to investigate whether it is possible to generate no-std-compliant structs from prost that do not use SystemTime.

This seems to be the main remaining issue. If there is no support for this directly in prost/tonic, do you see a way of working around that?

@soareschen
Copy link
Contributor Author

This seems to be the main remaining issue. If there is no support for this directly in prost/tonic, do you see a way of working around that?

It seems like SystemTime is not directly used, rather as an intermediary conversion between tendermint::Time and prost_types::TimeStamp.

We will have to fix tendermint::Time, as it is actually a newtype wrapper for chrono::DateTime without providing access to the underlying value. (Reminder that if we are defining newtypes next time, please do expose the underlying values as public fields) Once that is done, we should be able to convert chrono::DateTime to the nanos and seconds values directly.

@soareschen
Copy link
Contributor Author

@romac shall we merge this?

@romac
Copy link
Member

romac commented Sep 15, 2021

Let's go, we just need an entry in the .changelog folder using unclog.

@soareschen
Copy link
Contributor Author

we just need an entry in the .changelog folder using unclog.

Done.

Copy link
Member

@romac romac left a comment

Choose a reason for hiding this comment

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

Awesome work @soareschen, thanks a lot!

@romac romac merged commit 09420bc into master Sep 15, 2021
@romac romac deleted the soares/use-core-alloc branch September 15, 2021 13:09
romac added a commit that referenced this pull request Sep 15, 2021
romac added a commit that referenced this pull request Sep 16, 2021
* Use tendermint-rs master

* Re-introduce PartialEq and Eq trait bounds on error types

* Enable support for Secp256k1 signatures in tendermint-rs via its `secp256k1` feature

* Update Cargo.lock

* Ensure we do not introduce conflicts with #1156

* Add .changelog entry
hu55a1n1 pushed a commit to hu55a1n1/hermes that referenced this pull request Sep 13, 2022
…tems#1156)

* Use core and alloc crates for no_std compatibility

* Revert use of core:: back to std:: in ibc-proto crate

* Refactor some new uses of std

* Refactor more use of std.

* Fix formatting

* Fix more use of `std`

* Remove stale changes

* Remove some more use of std

* Use #![no_std] in ibc crate, with extern crate std and custom prelude

* Add changelog
hu55a1n1 pushed a commit to hu55a1n1/hermes that referenced this pull request Sep 13, 2022
* Use tendermint-rs master

* Re-introduce PartialEq and Eq trait bounds on error types

* Enable support for Secp256k1 signatures in tendermint-rs via its `secp256k1` feature

* Update Cargo.lock

* Ensure we do not introduce conflicts with informalsystems#1156

* Add .changelog entry
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
E: no-std External: support for no_std compatibility
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Tracking issue for no-std support
3 participants