Rustの200ラインの軽量ストリームの説明

Rustの200ラインの軽量ストリームの説明



軽量スレッド(コルーチン、コルーチン、グリーンスレッド)は、最新のプログラミング言語では非常に強力なメカニズムです。この記事では、Carl Fredrik Samsonが、Rastで軽量ストリームのランタイムを実装しようとしました。その過程で、それらが「内部」でどのように機能するかを説明しました。







また、この記事はそれほど新鮮ではないため、Rustコンパイラの最新のナイトリーバージョンで例を機能させるには、この記事のコードリポジトリある変更が必要になる可能性が高いことにも注意してください。







彼はほとんど自分で翻訳した。すべてのコメントについて書いてください-私はすぐにそれを修正します。文章をきちんと翻訳しようとしたのですが、読みやすく理解しやすいように書き直したところもありました。


グリーンスレッド



グリーンスレッドは、プログラミングの一般的な問題を解決します。コードがプロセッサをブロックしてリソースを浪費することは望ましくありません。これは、マルチタスクを使用することで解決されます。マルチタスクを使用すると、1つのコードの実行を一時停止し、実行のために別のコードを起動し、「コンテキスト」を切り替えることができます。







, — . — . , .







:







  • ()




, . — "-" ( - ). , , UI (User Interface — ) , . , , , .









. , - , , - . (yielding



control) . - , - . , /. , , - , .









, , . , . .







-, . , .. . , .







— x86-64. 16 :













,







, "callee saved". , — , , .. .







, . , . :







mov %rsp, %rax
      
      







. . , , ( )







: AT&T .







AT&T Rust. , . LLVM. LLVM , .







AT&T.







. , . %rax



, :







%rax    # 64   (8 )
%eax    #  32   "rax"
%ax     #  16   "rax"
%ah     #  8   "ax"  "rax"
%al     #  8   "rax"
      
      





+-----------------------------------------------------------------------+
|76543210 76543210 76543210 76543210 76543210 76543210 76543210 76543210|
+--------+--------+--------+--------+--------+--------+--------+--------+
|        |        |        |        |        |        |   %ah  |   %al  |
+--------+--------+--------+--------+--------+--------+--------+--------+
|        |        |        |        |        |        |       %ax       |
+--------+--------+--------+--------+--------+--------+-----------------+
|        |        |        |        |               %eax                |
+--------+--------+--------+--------+-----------------------------------+
|                                 %rax                                  |
+-----------------------------------------------------------------------+
      
      





, . 64, 64 .







"" . , 16 , 16 . , .. AT&T q



(quad-word — ) l



(long-word — ). movq



4 * 16 = 64 .







mov



, . AT&T.







16 x86-64. .







,



, .









"green_threads".







cargo init
      
      





- , , :







rustup override set nightly
      
      





main.rs



, llvm_asm!



:







#![feature(llvm_asm)]
      
      





, 48 , .







const SSIZE: isize = 48;
      
      





, . , , , , .







#[derive(Debug, Default)]
#[repr(C)]
struct ThreadContext {
    rsp: u64,
}
      
      





, "callee saved" , . ABI x86-64. , .







, - , #[repr(C)]



. ABI, , rsp



8 . ABI, .







fn hello() -> ! {
    println!("I LOVE WAKING UP ON A NEW STACK!");

    loop {}
}
      
      





, , .







, :







unsafe fn gt_switch(new: *const ThreadContext) {
    llvm_asm!("
        mov     0x00($0), %rsp
        ret
    "
    :
    : "r"(new)
    :
    : "alignstack" //    ,    
    );
}
      
      





. , . , rsp



( new.rsp



, , ). ?







ret



, . %rsp



, , , . ret



, .







, , .









, . . , :







unsafe



— , , . , .







gt_switch(new: *const ThreadContext)



ThreadContext



, .







llvm_asm!("



— . , - AT&T (-).







, — — mov 0x00($0), %rsp



. , , 0x00 ( ; ) $0 , rsp



. rsp



. , , .







$0



. . , 0, 1, 2 .., output



input



. , $0



.







$



, , , ( ), (, , x86 x86-64).







ret



— , , , . , .







output
:
      
      





. , . output



, , .







input
: "r"(new)
      
      





input



. "r"



— , (constraint



). , (, - ). "r"



, . — , , , .







clobber list
:
      
      





clobber list



— , , , , . , , , , . .. , .







options
: "alignstack"
      
      





— . : alignstack



, volatile



intel



. , . Windows alignstack



.









fn main() {
    let mut ctx = ThreadContext::default();
    let mut stack = vec![0_u8; SSIZE as usize];

    unsafe {
        let stack_bottom = stack.as_mut_ptr().offset(SSIZE);
        let sb_aligned = (stack_bottom as usize & !15) as *mut u8;
        std::ptr::write(sb_aligned.offset(-16) as *mut u64, hello as u64);
        ctx.rsp = sb_aligned.offset(-16) as u64;
        gt_switch(&mut ctx);
    }
}
      
      





. hello



( ), u64



, 64 , .







, — ( ). 48 0 47, 32 16 / .


|0          1           2          3           4       |4  5
|0123456789 012345|6789 0123456789 01|23456789 01234567|89 0123456789
|                 |                  |XXXXXXXX         |
|                 |                  |                 stack bottom
|0th byte         |16th byte         |32nd byte
      
      





, 16 (, 16 ?)







let sb_aligned = (stack_bottom as usize & !15) as *mut u8;



? Vec<u8>



, , 16 . , 16 . , .

, u64



u8



. u64



32-39, 8 . u64



- 32, , .







rsp



(Stack Pointer — ) 32 . u64



-, , .







cargo run



, :







dau@dau-work-pc:~/Projects/rust-programming-book/green_threads/green_threads$ cargo run
   Compiling green_threads v0.1.0 (/home/dau/Projects/rust-programming-book/green_threads/green_threads)
    Finished dev [unoptimized + debuginfo] target(s) in 0.44s
     Running `target/debug/green_threads`
I LOVE WAKING UP ON A NEW STACK!
      
      





? hello



, . : . .









. . "" " " — .







, . (push/pop) , . .







— , , Rust Programming Language.









. 64 8 . , u8



, , , 000



, 0008



0016



.













, .







stack pointer



, 16 , , , , 16 ( — ). , , , 0008



(, ).







( gt_switch



), .







: — hello



, , .







print!(
    "hello func address: 0x{addr:016X} ({addr})\n\n",
    addr = hello as usize
);

for i in (0..SSIZE).rev() {
    print!(
        "mem: {}, value: 0x{:02X}\n{}",
        stack.as_ptr().offset(i as isize) as usize,
        *stack.as_ptr().offset(i as isize),
        if i % 8 == 0 { "\n" } else { "" }
    );
}
      
      





:







hello func address: 0x0000560CD80B50B0 (94613164216496)

mem: 94613168839439, value: 0x00
mem: 94613168839438, value: 0x00
mem: 94613168839437, value: 0x00
mem: 94613168839436, value: 0x00
mem: 94613168839435, value: 0x00
mem: 94613168839434, value: 0x00
mem: 94613168839433, value: 0x00
mem: 94613168839432, value: 0x00

mem: 94613168839431, value: 0x00
mem: 94613168839430, value: 0x00
mem: 94613168839429, value: 0x56
mem: 94613168839428, value: 0x0C
mem: 94613168839427, value: 0xD8
mem: 94613168839426, value: 0x0B
mem: 94613168839425, value: 0x50
mem: 94613168839424, value: 0xB0

mem: 94613168839423, value: 0x00
mem: 94613168839422, value: 0x00
mem: 94613168839421, value: 0x00
mem: 94613168839420, value: 0x00
mem: 94613168839419, value: 0x00
mem: 94613168839418, value: 0x00
mem: 94613168839417, value: 0x00
mem: 94613168839416, value: 0x00

mem: 94613168839415, value: 0x00
mem: 94613168839414, value: 0x00
mem: 94613168839413, value: 0x00
mem: 94613168839412, value: 0x00
mem: 94613168839411, value: 0x00
mem: 94613168839410, value: 0x00
mem: 94613168839409, value: 0x00
mem: 94613168839408, value: 0x00

mem: 94613168839407, value: 0x00
mem: 94613168839406, value: 0x00
mem: 94613168839405, value: 0x00
mem: 94613168839404, value: 0x00
mem: 94613168839403, value: 0x00
mem: 94613168839402, value: 0x00
mem: 94613168839401, value: 0x00
mem: 94613168839400, value: 0x00

mem: 94613168839399, value: 0x00
mem: 94613168839398, value: 0x00
mem: 94613168839397, value: 0x00
mem: 94613168839396, value: 0x00
mem: 94613168839395, value: 0x00
mem: 94613168839394, value: 0x00
mem: 94613168839393, value: 0x00
mem: 94613168839392, value: 0x00

I LOVE WAKING UP ON A NEW STACK!
      
      





u64



, , ( — .).







, , , , 94613168839392



94613168839439



.







94613168839424



94613168839431



. — stack pointer



, , %rsp%



. , . ( — !!!)







0xB0, 0x50, 0x0B, 0xD8, 0x0C, 0x56, 0x00, 0x00



— ( ) hello()



, u8



.







, , 48 , , , .









, 8 , . , , , . " " (stack overflow), .







, , . 8 , , , -. , , , , , .









(growable — ) . , . , , , , .







Go. 8 , , . , , , . , GO ( ), .







, : ( Vec<u8>



) . , . , , .

, , - , . - , push()



( — .) . , , .

, , . , .









Windows x86-64 , x86-64 psABI. Windows , , , , , .







psABI :













, %rsp



— . , , base pointer, 16. 8 , , , . , - .







, stack_ptr + SSIZE - 16



, . - SSIZE



— .







. , ( — ) 8 . , rsp



16 , ABI.







- , stack_ptr + SSIZE - 16



. :







  • , stack_ptr + SSIZE



    ( 16 ), .. , .
  • , stack_ptr + SSIZE - 8



    , , 16 .


stack_ptr + SSIZE - 16



. 8 -16, -15, -14, ..., -9



(, , bottom of stack, .. ( — , , — , )).









, , , .







, , , .







, , 1024 , , . .









, , . : BEFORE.txt



( ) AFTER.txt



( ). , .







- , — .


#![feature(llvm_asm)]
#![feature(naked_functions)]

use std::io::Write;

const SSIZE: isize = 1024;
static mut S_PTR: *const u8 = 0 as *const u8;

#[derive(Debug, Default)]
#[repr(C)]
struct ThreadContext {
    rsp: u64,
    r15: u64,
    r14: u64,
    r13: u64,
    r12: u64,
    rbx: u64,
    rbp: u64,
}

fn print_stack(filename: &str) {
    let mut f = std::fs::File::create(filename).unwrap();
    unsafe {
        for i in (0..SSIZE).rev() {
            writeln!(
                f,
                "mem: {}, val: {}",
                S_PTR.offset(i as isize) as usize,
                *S_PTR.offset( i as isize)
            )
            .expect("Error writing to file.");
        }
    }
}

fn hello() {
    println!("I LOVE WAKING UP ON A NEW STACK!");
    print_stack("AFTER.txt");

    loop {}
}

unsafe fn gt_switch(new: *const ThreadContext) {
    llvm_asm!("
        mov     0x00($0), %rsp
        ret
        "
        :
        : "r"(new)
        :
        : "alignstack"
    );
}

fn main() {

    let mut ctx = ThreadContext::default();
    let mut stack = vec![0_u8; SSIZE as usize];
    let stack_ptr = stack.as_mut_ptr();

    unsafe {
        S_PTR = stack_ptr;
        std::ptr::write(stack_ptr.offset(SSIZE - 16) as *mut u64, hello as u64);
        print_stack("BEFORE.txt");
        ctx.rsp = stack_ptr.offset(SSIZE - 16) as u64;
        gt_switch(&mut ctx);
    }
}
      
      







, , , , , , " " (best practicies) . , , , - , , , PR .









, — main.rs



, , :







#![feature(llvm_asm)]
#![feature(naked_functions)]

use std::ptr;

const DEFAULT_STACK_SIZE: usize = 1024 * 1024 * 2;
const MAX_THREADS: usize = 4;

static mut RUNTIME: usize = 0;
      
      





: asm



naked_functions



, .







naked_functions





, "" "", - , . , , . #[naked]



. .







, naked_functions



RFC #1201.



Naked- . , , , .. naked- . ret



naked- ( , ABI), . .

DEFAULT_STACK_SIZE



2 , , . (MAX_THREADS



) 4, .. .







RUNTIME



— , (, , , , ).







- :







pub struct Runtime {
    threads: Vec<Thread>,
    current: usize,
}

#[derive(Debug, Eq, PartialEq)]
enum State {
    Available,
    Running,
    Ready,
}

struct Thread {
    id: usize,
    stack: Vec<u8>,
    ctx: ThreadContext,
    state: State,
}

#[derive(Debug, Default)]
#[repr(C)]
struct ThreadContext {
    rps: u64,
    r15: u64,
    r14, u64,
    r13: u64,
    r12: u64,
    rbx: u64,
    rbp: u64,
}
      
      





Runtime



. , . Thread



current



, , .







Thread



. , , id



. stack



, . ctx



, , . state



— .







State



— , :







  • Available



    — , .
  • Running



  • Ready



    — .


ThreadContext



, .







, " " , . x86-64 "callee saved".

:







impl Thread {
    fn new(id: usize) -> Self {
        Thread {
            id,
            stack: vec![0_u8; DEFAULT_STACK_SIZE],
            ctx: ThreadContext::default(),
            state: State::Available,
        }
    }
}
      
      





. Available



, , .







, . , .. . , , .







, , , . push()



, . , .



, Vec<T>



into_boxed_slice()



, Box<[T]>



— , . , .




impl Runtime



, .







impl Runtime {

    pub fn new() -> Self {
        let base_thread = Thread {
            id: 0,
            stack: vec![0_u8; DEFAULT_STACK_SIZE],
            ctx: ThreadContext::default(),
            state: State::Running,
        };

        let mut threads = vec![base_thread];
        let mut available_threads: Vec<Thread> = (1..MAX_THREADS).map(|i| Thread::new(i)).collect();
        threads.append(&mut available_threads);

        Runtime {
            threads,
            current: 0,
        }
    }

    // code of other methods is here
    // ...
}
      
      





Runtime



Running



. . , 0



, .







// ,       Runtime
//   ,      yield
//        .
//        
pub fn init(&self) {
    unsafe {
        let r_ptr: *const Runtime = self;
        RUNTIME = r_ptr as usize;
    }
}
      
      





. , , , yield



. , , , .







pub fn run(&mut self) -> ! {
    while self.t_yield() {};
    std::process::exit(0);
}
      
      





, . t_yield()



, false



, , , .







fn t_return(&mut self) {
    if self.current != 0 {
        std.threads[self.current].state = State::Available;
        self.t_yield();
    }
}
      
      





, . t_return



, .. return



. , — , , .







, . yield



. (spawned) , , , .. guard



( ). , t_return



guard



.







Available



, , (task), t_yield



, .







yield



:







fn t_yield(&mut self) -> bool {
    let mut post = self.current;
    while self.threads[pos].state != State::Ready {
        pos += 1;
        if pos == self.threads.len() {
            pos = 0;
        }
        if pos == self.current {
            return false;
        }
    }

    if self.threads[self.current].state != State::Available {
        self.threads[self.current].state = State::Ready;
    }

    self.threads[pos].state = State::Running;
    let old_pos = self.current;
    self.current = pos;

    unsafe {
        let old: *mut ThreadContext = &mut self.threads[old_pos].ctx;
        let new: *const ThreadContext = &self.threads[pos].ctx;

        llvm_asm!(
            "
            mov $0, %rdi
            mov $1, %rsi
            "
            :
            : "r"(old), "r"(new)
            :
            :
        );
        switch();
    }

    self.threads.len() > 0
}
      
      





. t_yield



, .. yield



(. — , ).







, - Ready



, , , . , .







Ready



, . , (round-robin). , .







. , ( Ready



) -, , ?



. , , (poll) . , IsReady



, , Pending



, - . Ready



, . ? , , , .

, , Running



Ready



.







switch



, . , , .







naked





naked . , . , , , . , #[naked]



, . "" "" ThreadContext



, . Linux %rdi



, — %rsi



.

self.threads.len() > 0



— , . Windows, Linux, , . std::hint::black_box



, , , , . , , . , .







spawn()



:







pub fn spawn(&mut self, f: fn()) {
    let available = self
        .threads
        .iter_mut()
        .find(|t| t.state == State::Available)
        .expect("no available thread.");

    let size == available.stack.len();

    unsafe {
        let s_ptr = available.stack.as_mut_ptr().offset(size as isize);
        let s_ptr = (s_ptr as usize & !15) as *mut u8;
        std::ptr::write(s_ptr.offset(-16) as *mut u64, guard as u64);
        std::ptr::write(s_ptr.offset(-24) as *mut u64, skip as u64);
        std::ptr::write(s_ptr.offset(-32) as *mut u64, f as u64);
        available.ctx.rsp = s_ptr.offset(-32) as u64;
    }
    available.state = State::Ready;
}

//       `impl Runtime`
      
      





, t_yield



, spawn



.







, , , , , psABI.







, , (.. Available



). , , , , . .







, ( u8



) .







unsafe-. , 16 . guard



, , . skip



, , f



, guard



16 . , , f



.







, . , f



, . base pointer . skip



guard



. guard



16 , ABI.

, , rsp



( ) . .







, Ready



, , , . , "" .







. , , . , , .







guard



, skip



switch





fn guard() {
    unsafe {
        let rt_ptr = RUNTIME as *mut Runtime;
        (*rt_ptr).t_return();
    };
}
      
      





, , , , , , t_return()



. , , , , t_return



, . Available



( ), t_yield



, .







#[naked]
fn skip() { }
      
      





skip



. #[naked]



, ret



, , . , guard



.







pub fn yield_thread() {
    unsafe {
        let rt_ptr = RUNTIME as *mut Runtime;
        (*rt_ptr).t_yield();
    };
}
      
      





, t_yield



. , , (), . , , .







, . , .







#[naked]
#[inline(never)]
unsafe fn switch() {
    llvm_asm!("
        mov     %rsp, 0x00(%rdi)
        mov     %r15, 0x08(%rdi)
        mov     %r14, 0x10(%rdi)
        mov     %r13, 0x18(%rdi)
        mov     %r12, 0x20(%rdi)
        mov     %rbx, 0x28(%rdi)
        mov     %rbp, 0x30(%rdi)

        mov     0x00(%rsi), %rsp
        mov     0x08(%rsi), %r15
        mov     0x10(%rsi), %r14
        mov     0x18(%rsi), %r13
        mov     0x20(%rsi), %r12
        mov     0x28(%rsi), %rbx
        mov     0x30(%rsi), %rbp
        "
    );
}
      
      





. , , , , , "" .







, , .







#[naked]



. , , , . , .







. #[inline(never)]



, . , , --release



.







main





fn main() {
    let mut runtime = Runtime::new();

    runtime.init();

    runtime.spawn(|| {
        println!("THREAD 1 STARTING");
        let id = 1;
        for i in 1..=10 {
            println!("thread: {} counter: {}", id, i);
            yield_thread();
        }
        println!("THREAD 1 FINISHED");
    });

    runtime.spawn(|| {
        println!("THREAD 2 STARTING");
        let id = 2;
        for i in 1..=15 {
            println!("thread: {} counter: {}", id i);
            yield_thread();
        }
        println!("THREAD 2 FINISHED");
    });

    runtime.run();
}
      
      





, , 0 9 15, . cargo run



, :







THREAD 1 STARTING
thread: 1 counter: 1
THREAD 2 STARTING
thread: 2 counter: 1
thread: 1 counter: 2
thread: 2 counter: 2
thread: 1 counter: 3
thread: 2 counter: 3
thread: 1 counter: 4
thread: 2 counter: 4
thread: 1 counter: 5
thread: 2 counter: 5
thread: 1 counter: 6
thread: 2 counter: 6
thread: 1 counter: 7
thread: 2 counter: 7
thread: 1 counter: 8
thread: 2 counter: 8
thread: 1 counter: 9
thread: 2 counter: 9
thread: 1 counter: 10
thread: 2 counter: 10
THREAD 1 FINISHED
thread: 2 counter: 11
thread: 2 counter: 12
thread: 2 counter: 13
thread: 2 counter: 14
thread: 2 counter: 15
THREAD 2 FINISHED
      
      





. , . 1 , 2 .









, . , , . !








All Articles