-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
25: Global singletons r=therealprof a=japaric This PR depends on #24 r? @rust-embedded/resources @jamesmunns this is the global singleton pattern I mentioned some time ago on IRC Co-authored-by: Jorge Aparicio <jorge@japaric.io>
- Loading branch information
Showing
14 changed files
with
318 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../logging/app2/.cargo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[package] | ||
authors = ["Jorge Aparicio <jorge@japaric.io>"] | ||
edition = "2018" | ||
name = "app" | ||
version = "0.1.0" | ||
|
||
[profile.release] | ||
codegen-units = 1 | ||
lto = true | ||
|
||
[dependencies] | ||
cortex-m = "0.5.7" | ||
cortex-m-semihosting = "0.3.1" | ||
log = { path = "../log" } | ||
rt = { path = "../rt" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
00000001 l .log 00000001 Goodbye | ||
00000000 l .log 00000001 Hello, world! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
0001 |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
#![no_main] | ||
#![no_std] | ||
|
||
use cortex_m::interrupt; | ||
use cortex_m_semihosting::{ | ||
debug, | ||
hio::{self, HStdout}, | ||
}; | ||
|
||
use log::{global_logger, log, GlobalLog}; | ||
use rt::entry; | ||
|
||
struct Logger; | ||
|
||
global_logger!(Logger); | ||
|
||
entry!(main); | ||
|
||
fn main() -> ! { | ||
log!("Hello, world!"); | ||
|
||
log!("Goodbye"); | ||
|
||
debug::exit(debug::EXIT_SUCCESS); | ||
|
||
loop {} | ||
} | ||
|
||
impl GlobalLog for Logger { | ||
fn log(&self, address: u8) { | ||
// we use a critical section (`interrupt::free`) to make the access to the | ||
// `static mut` variable interrupt safe which is required for memory safety | ||
interrupt::free(|_| unsafe { | ||
static mut HSTDOUT: Option<HStdout> = None; | ||
|
||
// lazy initialization | ||
if HSTDOUT.is_none() { | ||
HSTDOUT = Some(hio::hstdout()?); | ||
} | ||
|
||
let hstdout = HSTDOUT.as_mut().unwrap(); | ||
|
||
hstdout.write_all(&[address]) | ||
}).ok(); // `.ok()` = ignore errors | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
[package] | ||
name = "log" | ||
version = "0.1.0" | ||
authors = ["Jorge Aparicio <jorge@japaric.io>"] | ||
edition = "2018" | ||
|
||
[dependencies] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../logging/log/build.rs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../logging/log/log.x |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
#![no_std] | ||
|
||
// NEW! | ||
pub trait GlobalLog: Sync { | ||
fn log(&self, address: u8); | ||
} | ||
|
||
pub trait Log { | ||
type Error; | ||
|
||
fn log(&mut self, address: u8) -> Result<(), Self::Error>; | ||
} | ||
|
||
#[macro_export] | ||
macro_rules! log { | ||
// NEW! | ||
($string:expr) => { | ||
unsafe { | ||
extern "Rust" { | ||
static LOGGER: &'static dyn $crate::GlobalLog; | ||
} | ||
|
||
#[export_name = $string] | ||
#[link_section = ".log"] | ||
static SYMBOL: u8 = 0; | ||
|
||
$crate::GlobalLog::log(LOGGER, &SYMBOL as *const u8 as usize as u8) | ||
} | ||
}; | ||
|
||
($logger:expr, $string:expr) => {{ | ||
#[export_name = $string] | ||
#[link_section = ".log"] | ||
static SYMBOL: u8 = 0; | ||
|
||
$crate::Log::log(&mut $logger, &SYMBOL as *const u8 as usize as u8) | ||
}}; | ||
} | ||
|
||
// NEW! | ||
#[macro_export] | ||
macro_rules! global_logger { | ||
($logger:expr) => { | ||
#[no_mangle] | ||
pub static LOGGER: &dyn $crate::GlobalLog = &$logger; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../logging/rt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
# Global singletons | ||
|
||
In this section we'll cover how to implement a global, shared singleton. The | ||
embedded Rust book covered local, owned singletons which are pretty much unique | ||
to Rust. Global singletons are essentially the singleton pattern you see in C | ||
and C++; they are not specific to embedded development but since they involve | ||
symbols they seemed a good fit for the embedonomicon. | ||
|
||
> **TODO**(resources team) link "the embedded Rust book" to the singletons | ||
> section when it's up | ||
To illustrate this section we'll extend the logger we developed in the last | ||
section to support global logging. The result will be very similar to the | ||
`#[global_allocator]` feature covered in the embedded Rust book. | ||
|
||
> **TODO**(resources team) link `#[global_allocator]` to the collections chapter | ||
> of the book when it's in a more stable location. | ||
Here's the summary of what we want to: | ||
|
||
In the last section we created a `log!` macro to log messages through a specific | ||
logger, a value that implements the `Log` trait. The syntax of the `log!` macro | ||
is `log!(logger, "String")`. We want to extend the macro such that | ||
`log!("String")` also works. Using the `logger`-less version should log the | ||
message through a global logger; this is how `std::println!` works. We'll also | ||
need a mechanism to declare what the global logger is; this is the part that's | ||
similar to `#[global_allocator]`. | ||
|
||
It could be that the global logger is declared in the top crate and it could | ||
also be that the type of the global logger is defined in the top crate. In this | ||
scenario the dependencies can *not* know the exact type of the global logger. To | ||
support this scenario we'll need some indirection. | ||
|
||
Instead of hardcoding the type of the global logger in the `log` crate we'll | ||
declare only the *interface* of the global logger in that crate. That is we'll | ||
add a new trait, `GlobalLog`, to the `log` crate. The `log!` macro will also | ||
have to make use of that trait. | ||
|
||
``` console | ||
$ cat ../log/src/lib.rs | ||
``` | ||
|
||
``` rust | ||
{{#include ../ci/singleton/log/src/lib.rs}} | ||
``` | ||
|
||
There's quite a bit to unpack here. | ||
|
||
Let's start with the trait. | ||
|
||
``` rust | ||
{{#include ../ci/singleton/log/src/lib.rs:4:6}} | ||
``` | ||
|
||
Both `GlobalLog` and `Log` have a `log` method. The difference is that | ||
`GlobalLog.log` takes a shared reference to the receiver (`&self`). This is | ||
necessary because the global logger will be a `static` variable. More on that | ||
later. | ||
|
||
The other difference is that `GlobalLog.log` doesn't return a `Result`. This | ||
means that it can *not* report errors to the caller. This is not a strict | ||
requirement for traits used to implement global singletons. Error handling in | ||
global singletons is fine but then all users of the global version of the `log!` | ||
macro have to agree on the error type. Here we are simplifying the interface a | ||
bit by having the `GlobalLog` implementer deal with the errors. | ||
|
||
Yet another difference is that `GlobalLog` requires that the implementer is | ||
`Sync`, that is that it can be shared between threads. This is a requirement for | ||
values placed in `static` variables; their types must implement the `Sync` | ||
trait. | ||
|
||
At this point it may not be entirely clear why the interface has to look this | ||
way. The other parts of the crate will make this clearer so keep reading. | ||
|
||
Next up is the `log!` macro: | ||
|
||
``` rust | ||
{{#include ../ci/singleton/log/src/lib.rs:17:29}} | ||
``` | ||
|
||
When called without a specific `$logger` the macros uses an `extern` `static` | ||
variable called `LOGGER` to log the message. This variable *is* the global | ||
logger that's defined somewhere else; that's why we use the `extern` block. We | ||
saw this pattern in the [main interface] chapter. | ||
|
||
[main interface]: /main.html | ||
|
||
We need to declare a type for `LOGGER` or the code won't type check. We don't | ||
know the concrete type of `LOGGER` at this point but we know, or rather require, | ||
that it implements the `GlobalLog` trait so we can use a trait object here. | ||
|
||
The rest of the macro expansion looks very similar to the expansion of the local | ||
version of the `log!` macro so I won't explain it here as it's explained in the | ||
[previous] chapter. | ||
|
||
[previous]: /logging.html | ||
|
||
Now that we know that `LOGGER` has to be a trait object it's clearer why we | ||
omitted the associated `Error` type in `GlobalLog`. If we had not omitted then | ||
we would have need to pick a type for `Error` in the type signature of `LOGGER`. | ||
This is what I earlier meant by "all users of `log!` would need to agree on the | ||
error type". | ||
|
||
Now the final piece: the `global_logger!` macro. It could have been a proc macro | ||
attribute but it's easier to write a `macro_rules!` macro. | ||
|
||
``` rust | ||
{{#include ../ci/singleton/log/src/lib.rs:41:47}} | ||
``` | ||
|
||
This macro creates the `LOGGER` variable that `log!` uses. Because we need a | ||
stable ABI interface we use the `no_mangle` attribute. This way the symbol name | ||
of `LOGGER` will be "LOGGER" which is what the `log!` macro expects. | ||
|
||
The other important bit is that the type of this static variable must exactly | ||
match the type used in the expansion of the `log!` macro. If they don't match | ||
Bad Stuff will happen due to ABI mismatch. | ||
|
||
Let's write an example that uses this new global logger functionality. | ||
|
||
``` console | ||
$ cat src/main.rs | ||
``` | ||
|
||
``` rust | ||
{{#include ../ci/singleton/app/src/main.rs}} | ||
``` | ||
|
||
> **TODO**(resources team) use `cortex_m::Mutex` instead of a `static mut` | ||
> variable when `const fn` is stabilized. | ||
We had to add `cortex-m` to the dependencies. | ||
|
||
``` console | ||
$ tail -n5 Cargo.toml | ||
``` | ||
|
||
``` text | ||
{{#include ../ci/singleton/app/Cargo.toml:11:15}} | ||
``` | ||
|
||
This is a port of one of the examples written in the [previous] section. The | ||
output is the same as what we got back there. | ||
|
||
``` console | ||
$ cargo run | xxd -p | ||
``` | ||
|
||
``` text | ||
{{#include ../ci/singleton/app/dev.out}} | ||
``` | ||
|
||
``` console | ||
$ cargo objdump --bin app -- -t | grep '\.log' | ||
``` | ||
|
||
``` text | ||
{{#include ../ci/singleton/app/dev.objdump}} | ||
``` | ||
|
||
--- | ||
|
||
Some readers may be concerned about this implementation of global singletons not | ||
being zero cost because it uses trait objects which involve dynamic dispatch, | ||
that is method calls are performed through a vtable lookup. | ||
|
||
However, it appears that LLVM is smart enough to eliminate the dynamic dispatch | ||
when compiling with optimizations / LTO. This can be confirmed by searching for | ||
`LOGGER` in the symbol table. | ||
|
||
``` console | ||
$ cargo objdump --bin app --release -- -t | grep LOGGER | ||
``` | ||
|
||
``` text | ||
{{#include ../ci/singleton/app/release.objdump}} | ||
``` | ||
|
||
If the `static` is missing that means that there is no vtable and that LLVM was | ||
capable of transforming all the `LOGGER.log` calls into `Logger.log` calls. |