Rust The Embedonomiconをやる④

tomo-wait-for-it-yuki.hatenablog.com

続きです。 最後の7. Global singletonsをやっていきます。

Global singletons

ログ機能をグローバルなオブジェクト経由で実現するように修正します。 log!(logger, "String")で使用していたログmacroを、log!("String")で使えるようにします。

#[global_allocator]に似たような仕組みを作るようです。global_allocatorがどのような仕組みなのか気になっていたので、願ったり叶ったりです。

lob/src/lib.rs

pub trait GlobalLog: Sync {
    fn log(&self, address: u8);
}

...

#[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)
        }
    };
// 今まであった`log!(logger, "String")`スタイルのパターン
}

#[macro_export]
macro_rules! global_logger {
    ($logger:expr) => {
        #[no_mangle]
        pub static LOGGER: &dyn $crate::GlobalLog = &$logger;
    };
}

なるほど。global_logger macroでstaticかつglobalなLOGGERを定義しておいて、log macroでは、そのLOGGERを使ってログ出力するわけですね。

追加したmacroを使ってアプリケーションを作成します。 app/src/main.rs

#![no_main]
#![no_std]

...

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
    }
}

Logger構造体を定義し、GlabalLog traitを実装しています。 なるほどねー、という感じです。 global_allocとも使い方が一緒なので、おおまかにはこんな感じになっている、というのもうなづけます。

おわりに

これで、Embedonomiconシリーズの写経が完了しました。 Rustにおけるリンカの使いこなしや、stableでのアセンブラの使い方など、多くのことを学ぶことができるチュートリアルでした。 正直、やっていてかなり面白かったです。 ただし、Rustをある程度知っていて、低レイヤ(特にリンカ)の知識があることが前提の印象を受けました。そのあたりの解説は、ドキュメント内にありません。

これからお目当てのriscv-rtをハックしていこうと思います。