From d12d5d35ec293ccf8f1717435d4ac324fbc9b358 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sun, 25 Aug 2024 20:03:57 -0400 Subject: [PATCH 01/80] Update SDL binding version --- vm/Cargo.lock | 8 ++++---- vm/Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vm/Cargo.lock b/vm/Cargo.lock index 0c9260a..86543d8 100644 --- a/vm/Cargo.lock +++ b/vm/Cargo.lock @@ -28,9 +28,9 @@ checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" [[package]] name = "sdl2" -version = "0.35.2" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a" +checksum = "3b498da7d14d1ad6c839729bd4ad6fc11d90a57583605f3b4df2cd709a9cd380" dependencies = [ "bitflags", "lazy_static", @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "sdl2-sys" -version = "0.35.2" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0" +checksum = "951deab27af08ed9c6068b7b0d05a93c91f0a8eb16b6b816a5e73452a43521d3" dependencies = [ "cfg-if", "libc", diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 61f472e..771b17c 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -6,8 +6,8 @@ version = "0.2.0" edition = "2021" [dependencies] -sdl2 = "0.35.2" -libc = "0.2" +sdl2 = "0.37.0" +libc = "0.2" # needed for mmap [features] count_insns = [] From a1ef3faa3f3cda2f333be8067d174ca50703b32d Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sun, 25 Aug 2024 23:09:07 -0400 Subject: [PATCH 02/80] Remove callback logic --- vm/src/image.rs | 0 vm/src/main.rs | 5 ++ vm/src/sys/mod.rs | 26 ++---- vm/src/sys/net.rs | 36 ++++---- vm/src/sys/time.rs | 79 ------------------ vm/src/sys/window.rs | 191 +------------------------------------------ vm/src/vm.rs | 31 ++++++- 7 files changed, 63 insertions(+), 305 deletions(-) delete mode 100644 vm/src/image.rs diff --git a/vm/src/image.rs b/vm/src/image.rs deleted file mode 100644 index e69de29..0000000 diff --git a/vm/src/main.rs b/vm/src/main.rs index d1a3012..91b5d1e 100644 --- a/vm/src/main.rs +++ b/vm/src/main.rs @@ -87,6 +87,7 @@ fn run_program(mutex: &mut Arc>) -> Value drop(vm); + /* loop { let mut vm = mutex.lock().unwrap(); @@ -126,6 +127,10 @@ fn run_program(mutex: &mut Arc>) -> Value } } } + */ + + + todo!(); } fn main() diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index ab725be..0052672 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -100,11 +100,8 @@ pub struct SysState /// Weak reference to a mutex for the VM mutex: Weak>, - /// Time subsystem state - pub time_state: TimeState, - /// Network subsystem state - pub net_state: NetState, + } impl SysState @@ -114,8 +111,7 @@ impl SysState let mut sys_state = Self { syscalls: [None; SYSCALL_TBL_LEN], mutex: Weak::new(), - time_state: TimeState::new(), - net_state: NetState::default(), + //net_state: NetState::default(), }; sys_state.init_syscalls(); @@ -184,24 +180,18 @@ impl SysState self.reg_syscall(GETCHAR, SysCallFn::Fn0_1(getchar)); self.reg_syscall(TIME_CURRENT_MS, SysCallFn::Fn0_1(time_current_ms)); - self.reg_syscall(TIME_DELAY_CB, SysCallFn::Fn2_0(time_delay_cb)); + //self.reg_syscall(TIME_DELAY_CB, SysCallFn::Fn2_0(time_delay_cb)); self.reg_syscall(WINDOW_CREATE, SysCallFn::Fn4_1(window_create)); self.reg_syscall(WINDOW_DRAW_FRAME, SysCallFn::Fn2_0(window_draw_frame)); - self.reg_syscall(WINDOW_ON_MOUSEMOVE, SysCallFn::Fn2_0(window_on_mousemove)); - self.reg_syscall(WINDOW_ON_MOUSEDOWN, SysCallFn::Fn2_0(window_on_mousedown)); - self.reg_syscall(WINDOW_ON_MOUSEUP, SysCallFn::Fn2_0(window_on_mouseup)); - self.reg_syscall(WINDOW_ON_KEYDOWN, SysCallFn::Fn2_0(window_on_keydown)); - self.reg_syscall(WINDOW_ON_KEYUP, SysCallFn::Fn2_0(window_on_keyup)); - self.reg_syscall(WINDOW_ON_TEXTINPUT, SysCallFn::Fn2_0(window_on_textinput)); self.reg_syscall(AUDIO_OPEN_OUTPUT, SysCallFn::Fn4_1(audio_open_output)); - self.reg_syscall(NET_LISTEN, SysCallFn::Fn2_1(net_listen)); - self.reg_syscall(NET_ACCEPT, SysCallFn::Fn4_1(net_accept)); - self.reg_syscall(NET_READ, SysCallFn::Fn3_1(net_read)); - self.reg_syscall(NET_WRITE, SysCallFn::Fn3_1(net_write)); - self.reg_syscall(NET_CLOSE, SysCallFn::Fn1_0(net_close)); + //self.reg_syscall(NET_LISTEN, SysCallFn::Fn2_1(net_listen)); + //self.reg_syscall(NET_ACCEPT, SysCallFn::Fn4_1(net_accept)); + //self.reg_syscall(NET_READ, SysCallFn::Fn3_1(net_read)); + //self.reg_syscall(NET_WRITE, SysCallFn::Fn3_1(net_write)); + //self.reg_syscall(NET_CLOSE, SysCallFn::Fn1_0(net_close)); } } diff --git a/vm/src/sys/net.rs b/vm/src/sys/net.rs index b3a69ad..2e1f3ef 100644 --- a/vm/src/sys/net.rs +++ b/vm/src/sys/net.rs @@ -6,6 +6,7 @@ use std::io::{self, Read, Write, Error}; use std::sync::{Arc, Weak, Mutex}; use crate::vm::{VM, Value, ExitReason}; +/* // State for the networking subsystem pub struct NetState { @@ -15,7 +16,9 @@ pub struct NetState /// Map of open sockets sockets: HashMap, } +*/ +/* impl Default for NetState { fn default() -> Self @@ -27,25 +30,9 @@ impl Default for NetState } } } +*/ -// State associated with a socket -enum Socket -{ - Listen { - listener: TcpListener, - - /// Incoming connections - incoming: VecDeque, - }, - - Stream { - stream: TcpStream, - - // Read buffer - read_buf: Vec, - } -} - +/* /// TCP listening thread fn listen_thread( vm_mutex: Weak>, @@ -91,7 +78,9 @@ fn listen_thread( } } } +*/ +/* // Syscall to create a TCP listening socket to accept incoming connections // u64 socket_id = net_listen( // const char* listen_addr, // Network interface address to listen on, null for any address @@ -142,7 +131,9 @@ pub fn net_listen( // Return the socket id Value::from(socket_id) } +*/ +/* /// TCP read thread fn read_thread( vm_mutex: Weak>, @@ -201,7 +192,9 @@ fn read_thread( } } } +*/ +/* // Syscall to accept a new connection // Writes the client address in the buffer you specify // u64 socket_id = net_accept(u64 socket_id, char* client_addr, u64 client_addr_len, callback on_incoming_data) @@ -270,7 +263,9 @@ pub fn net_accept( _ => panic!() } } +*/ +/* // Syscall to read data from a given socket into a buffer you specify // u64 num_bytes_read = net_read(u64 socket_id, void* buf_ptr, u64 buf_len) pub fn net_read( @@ -302,7 +297,9 @@ pub fn net_read( _ => panic!("invalid socket id {} in net_read", socket_id) } } +*/ +/* // Syscall to write data on a given socket // u64 num_bytes = net_write(u64 socket_id, void* buf_ptr, u64 buf_len); pub fn net_write( @@ -327,7 +324,9 @@ pub fn net_write( _ => panic!() } } +*/ +/* // Syscall to close a socket // net_close(u64 socket_id) pub fn net_close( @@ -355,3 +354,4 @@ pub fn net_close( // This drops the socket net_state.sockets.remove(&socket_id); } +*/ diff --git a/vm/src/sys/time.rs b/vm/src/sys/time.rs index dd636fb..0140313 100644 --- a/vm/src/sys/time.rs +++ b/vm/src/sys/time.rs @@ -1,30 +1,6 @@ use std::time::{SystemTime, UNIX_EPOCH}; use crate::vm::{VM, Value}; -// Callback function to be run at a given time stamp -#[derive(Debug, Copy, Clone)] -struct DelayCb -{ - time_ms: u64, - pc: u64, -} - -pub struct TimeState -{ - // List of delay callbacks - delay_cbs: Vec, -} - -impl TimeState -{ - pub fn new() -> Self - { - Self { - delay_cbs: Vec::default(), - } - } -} - /// Get the current time stamp in milliseconds pub fn get_time_ms() -> u64 { @@ -36,58 +12,3 @@ pub fn time_current_ms(vm: &mut VM) -> Value { Value::from(get_time_ms()) } - -/// Call a callback function after a given delay in milliseconds -pub fn time_delay_cb(vm: &mut VM, delay_ms: Value, callback_pc: Value) -{ - let delay_ms = delay_ms.as_u64(); - let callback_pc = callback_pc.as_u64(); - - let time_ms = get_time_ms(); - - let cb_entry = DelayCb { - time_ms: time_ms + delay_ms, - pc: callback_pc - }; - - // Add the new callback to the list - let time_state = &mut vm.sys_state.time_state; - time_state.delay_cbs.push(cb_entry); - - // Sort the callbacks by decreasing trigger time - time_state.delay_cbs.sort_by(|a, b| b.time_ms.cmp(&a.time_ms)); -} - -/// Compute the time untl the next delay callback needs to run -pub fn time_until_next_cb(vm: &VM) -> Option -{ - let time_state = &vm.sys_state.time_state; - - let last_cb = time_state.delay_cbs.last(); - - match last_cb { - None => return None, - Some(cb) => { - let cb_time = cb.time_ms; - let cur_time = get_time_ms(); - let time_to = if cb_time > cur_time { cb_time - cur_time } else { 0 }; - return Some(time_to); - } - } -} - -/// Get the list of PCs for callbacks to be run now -pub fn get_cbs_to_run(vm: &mut VM) -> Vec -{ - let time_state = &mut vm.sys_state.time_state; - - // Extract callbacks to run and extract the PCs - let cur_time_ms = get_time_ms(); - let cbs_to_run = time_state.delay_cbs.iter().filter(|cb| cb.time_ms <= cur_time_ms); - let pcs_to_run = cbs_to_run.map(|cb| cb.pc).collect(); - - // Remove the callbacks to run from the list - time_state.delay_cbs.retain(|cb| cb.time_ms > cur_time_ms); - - return pcs_to_run; -} diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs index cbf2c7a..016d609 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/sys/window.rs @@ -45,16 +45,6 @@ struct Window<'a> canvas: sdl2::render::Canvas, texture_creator: sdl2::render::TextureCreator, texture: Option>, - - // Callbacks for mouse events - cb_mousemove: u64, - cb_mousedown: u64, - cb_mouseup: u64, - - // Callbacks for keyboard events - cb_keydown: u64, - cb_keyup: u64, - cb_textinput: u64, } // Note: we're leaving this global to avoid the Window lifetime @@ -108,12 +98,6 @@ pub fn window_create(vm: &mut VM, width: Value, height: Value, title: Value, fla canvas, texture_creator, texture: None, - cb_mousemove: 0, - cb_mousedown: 0, - cb_mouseup: 0, - cb_keydown: 0, - cb_keyup: 0, - cb_textinput: 0, }; unsafe { @@ -165,44 +149,11 @@ pub fn window_draw_frame(vm: &mut VM, window_id: Value, src_addr: Value) window.canvas.present(); } -pub fn window_on_mousemove(vm: &mut VM, window_id: Value, cb: Value) -{ - let window = get_window(window_id.as_u32()); - window.cb_mousemove = cb.as_u64(); -} - -pub fn window_on_mousedown(vm: &mut VM, window_id: Value, cb: Value) -{ - let window = get_window(window_id.as_u32()); - window.cb_mousedown = cb.as_u64(); -} - -pub fn window_on_mouseup(vm: &mut VM, window_id: Value, cb: Value) -{ - let window = get_window(window_id.as_u32()); - window.cb_mouseup = cb.as_u64(); -} -pub fn window_on_keydown(vm: &mut VM, window_id: Value, cb: Value) -{ - let window = get_window(window_id.as_u32()); - window.cb_keydown = cb.as_u64(); -} -pub fn window_on_keyup(vm: &mut VM, window_id: Value, cb: Value) -{ - let window = get_window(window_id.as_u32()); - window.cb_keyup = cb.as_u64(); -} -pub fn window_on_textinput(vm: &mut VM, window_id: Value, cb: Value) -{ - let window = get_window(window_id.as_u32()); - let video_subsystem = get_video_subsystem(); - video_subsystem.text_input().start(); - window.cb_textinput = cb.as_u64(); -} +/* /// Process SDL events pub fn process_events(vm: &mut VM) -> ExitReason { @@ -262,102 +213,13 @@ pub fn process_events(vm: &mut VM) -> ExitReason return ExitReason::default(); } - -// TODO: functions to process window-related events -// TODO: we should return the exit reason? -// this is gonna be awkward if we have audio processing threads/processes and such? -// though I suppose exit would just end those processes - -// TODO: this is just for testing -// we should handle window-related events here instead -fn window_call_mousemove(vm: &mut VM, window_id: u32, x: i32, y: i32) -> ExitReason -{ - let window = get_window(0); - let cb = window.cb_mousemove; - - if cb == 0 { - return ExitReason::default(); - } - - vm.call(cb, &[Value::from(window.window_id), Value::from(x), Value::from(y)]) -} - -/* -MouseButtonDown { - timestamp: u32, - window_id: u32, - which: u32, => this is a mouse id - mouse_btn: MouseButton, - x: i32, - y: i32, -}, */ -fn window_call_mousedown(vm: &mut VM, window_id: u32, mouse_btn: MouseButton, x: i32, y: i32) -> ExitReason -{ - let window = get_window(0); - let cb = window.cb_mousedown; - if cb == 0 { - return ExitReason::default(); - } - // TODO: ignore SDL_TOUCH_MOUSEID - // where is that defined in Rust? - // or only support mouse id 0? - //println!("mouse_id={}", mouse_id); - - let btn_id = match mouse_btn { - MouseButton::Left => 0, - MouseButton::Middle => 1, - MouseButton::Right => 2, - MouseButton::X1 => 3, - MouseButton::X2 => 4, - MouseButton::Unknown => { - return ExitReason::default(); - } - }; - vm.call(cb, &[ - Value::from(window.window_id), - Value::from(btn_id), - Value::from(x), - Value::from(y), - ]) -} - -fn window_call_mouseup(vm: &mut VM, window_id: u32, mouse_btn: MouseButton, x: i32, y: i32) -> ExitReason -{ - let window = get_window(0); - let cb = window.cb_mouseup; - - if cb == 0 { - return ExitReason::default(); - } - - // TODO: ignore SDL_TOUCH_MOUSEID - // where is that defined in Rust? - // or only support mouse id 0? - //println!("mouse_id={}", mouse_id); - - let btn_id = match mouse_btn { - MouseButton::Left => 0, - MouseButton::Middle => 1, - MouseButton::Right => 2, - MouseButton::X1 => 3, - MouseButton::X2 => 4, - MouseButton::Unknown => { - return ExitReason::default(); - } - }; - vm.call(cb, &[ - Value::from(window.window_id), - Value::from(btn_id), - Value::from(x), - Value::from(y), - ]) -} +/* fn translate_keycode(sdl_keycode: Keycode) -> Option { use crate::sys::constants::*; @@ -425,51 +287,4 @@ fn translate_keycode(sdl_keycode: Keycode) -> Option _ => None } } - -fn window_call_keydown(vm: &mut VM, window_id: u32, keycode: Keycode) -> ExitReason -{ - let window = get_window(0); - let cb = window.cb_keydown; - - if cb == 0 { - return ExitReason::default(); - } - - let keycode = translate_keycode(keycode); - - if let Some(keycode) = keycode { - vm.call(cb, &[Value::from(window.window_id), Value::from(keycode)]) - } else { - ExitReason::default() - } -} - -fn window_call_keyup(vm: &mut VM, window_id: u32, keycode: Keycode) -> ExitReason -{ - let window = get_window(0); - let cb = window.cb_keyup; - - if cb == 0 { - return ExitReason::default(); - } - - let keycode = translate_keycode(keycode); - - if let Some(keycode) = keycode { - vm.call(cb, &[Value::from(window.window_id), Value::from(keycode)]) - } else { - ExitReason::default() - } -} - -fn window_call_textinput(vm: &mut VM, window_id: u32, utf8_byte: u8) -> ExitReason -{ - let window = get_window(0); - let cb = window.cb_textinput; - - if cb == 0 { - return ExitReason::default(); - } - - vm.call(cb, &[Value::from(window.window_id), Value::from(utf8_byte)]) -} +*/ diff --git a/vm/src/vm.rs b/vm/src/vm.rs index ba0c133..9ccde82 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -1,3 +1,4 @@ +use std::sync::{Arc, Mutex}; use std::mem::{transmute, size_of}; use std::collections::HashSet; use std::ffi::CStr; @@ -520,6 +521,32 @@ impl Default for ExitReason } } + + + + + +pub struct Thread +{ + // Thread id + pub tid: u64, + + // Parent VM + pub vm: Arc>, + + // Value stack + stack: Vec, + + // List of stack frames (activation records) + frames: Vec, +} + + + + + + + pub struct VM { // Host system state @@ -538,8 +565,8 @@ pub struct VM frames: Vec, // Count of executed instructions - #[cfg(feature = "count_insns")] - insn_count: u64, + //#[cfg(feature = "count_insns")] + //insn_count: u64, } impl VM From c77d0dc54debefd5fbf30baf98b635f93f3ff447 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sun, 25 Aug 2024 23:28:26 -0400 Subject: [PATCH 03/80] Get rid of ExitReason --- vm/src/main.rs | 13 ++----------- vm/src/sys/audio.rs | 4 +++- vm/src/sys/net.rs | 2 +- vm/src/sys/window.rs | 2 +- vm/src/vm.rs | 27 ++++----------------------- 5 files changed, 11 insertions(+), 37 deletions(-) diff --git a/vm/src/main.rs b/vm/src/main.rs index 91b5d1e..d8d7698 100644 --- a/vm/src/main.rs +++ b/vm/src/main.rs @@ -15,7 +15,7 @@ use std::thread::sleep; use std::time::Duration; use std::process::exit; use std::sync::{Arc, Mutex}; -use crate::vm::{VM, Value, MemBlock, ExitReason}; +use crate::vm::{VM, Value, MemBlock}; use crate::asm::{Assembler}; use crate::sys::{SysState}; use crate::utils::{thousands_sep}; @@ -74,16 +74,7 @@ fn run_program(mutex: &mut Arc>) -> Value { let mut vm = mutex.lock().unwrap(); - match vm.call(0, &[]) - { - ExitReason::Exit(val) => { - return val; - } - - // Keep processig events - ExitReason::Return(val) => { - } - } + let result = vm.call(0, &[]); drop(vm); diff --git a/vm/src/sys/audio.rs b/vm/src/sys/audio.rs index bf23954..183e1aa 100644 --- a/vm/src/sys/audio.rs +++ b/vm/src/sys/audio.rs @@ -1,6 +1,6 @@ use sdl2::audio::{AudioCallback, AudioSpecDesired, AudioDevice}; use std::sync::{Arc, Weak, Mutex}; -use crate::vm::{Value, VM, ExitReason}; +use crate::vm::{Value, VM}; use crate::sys::{get_sdl_context}; use crate::sys::constants::*; @@ -34,6 +34,7 @@ impl AudioCallback for AudioCB let arc = self.vm.upgrade().unwrap(); let mut vm = arc.lock().unwrap(); + /* match vm.call(self.cb, &[Value::from(self.num_channels), Value::from(samples_per_chan)]) { ExitReason::Return(ptr) => { let mem_slice: &[i16] = vm.get_heap_slice(ptr.as_usize(), output_len); @@ -41,6 +42,7 @@ impl AudioCallback for AudioCB } _ => panic!() } + */ } } diff --git a/vm/src/sys/net.rs b/vm/src/sys/net.rs index 2e1f3ef..52e3edf 100644 --- a/vm/src/sys/net.rs +++ b/vm/src/sys/net.rs @@ -4,7 +4,7 @@ use std::thread; use std::net::{TcpListener, TcpStream}; use std::io::{self, Read, Write, Error}; use std::sync::{Arc, Weak, Mutex}; -use crate::vm::{VM, Value, ExitReason}; +use crate::vm::{VM, Value}; /* // State for the networking subsystem diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs index 016d609..51c624e 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/sys/window.rs @@ -13,7 +13,7 @@ use sdl2::pixels::PixelFormatEnum; use std::time::Duration; use crate::sys::{SysState, get_sdl_context}; -use crate::vm::{VM, Value, ExitReason}; +use crate::vm::{VM, Value}; /// SDL video subsystem /// This is a global variable because it doesn't implement diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 9ccde82..015db79 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -507,21 +507,6 @@ struct StackFrame argc: usize, } -pub enum ExitReason -{ - Return(Value), - Exit(Value), - //Panic, -} - -impl Default for ExitReason -{ - fn default() -> ExitReason { - ExitReason::Return(Value::from(0)) - } -} - - @@ -698,7 +683,7 @@ impl VM } /// Call a function at a given address - pub fn call(&mut self, callee_pc: u64, args: &[Value]) -> ExitReason + pub fn call(&mut self, callee_pc: u64, args: &[Value]) -> Value { assert!(self.stack.len() == 0); assert!(self.frames.len() == 0); @@ -1568,7 +1553,7 @@ impl VM let val = self.pop(); self.stack.clear(); self.frames.clear(); - return ExitReason::Exit(val); + return val; } Op::ret => { @@ -1582,7 +1567,7 @@ impl VM if self.frames.len() == 1 { self.stack.clear(); self.frames.clear(); - return ExitReason::Return(ret_val); + return ret_val; } assert!(self.frames.len() > 0); @@ -1619,11 +1604,7 @@ mod tests let result = vm.call(0, &[]); assert!(vm.stack.len() == 0 && vm.frames.len() == 0); - match result - { - ExitReason::Exit(value) => value, - ExitReason::Return(value) => value, - } + result } fn eval_i64(src: &str, expected: i64) From ac129d8309cc85a532f77b9df74a6b0158b12ec7 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 26 Aug 2024 15:42:30 -0400 Subject: [PATCH 04/80] Remove SysState --- vm/src/main.rs | 3 +- vm/src/sys/audio.rs | 4 ++ vm/src/sys/mod.rs | 112 ++++++++----------------------------------- vm/src/sys/window.rs | 2 +- vm/src/vm.rs | 22 ++++----- 5 files changed, 36 insertions(+), 107 deletions(-) diff --git a/vm/src/main.rs b/vm/src/main.rs index d8d7698..053ddb6 100644 --- a/vm/src/main.rs +++ b/vm/src/main.rs @@ -17,7 +17,6 @@ use std::process::exit; use std::sync::{Arc, Mutex}; use crate::vm::{VM, Value, MemBlock}; use crate::asm::{Assembler}; -use crate::sys::{SysState}; use crate::utils::{thousands_sep}; /// Command-line options @@ -150,7 +149,7 @@ fn main() } let vm = result.unwrap(); - let mut mutex = SysState::get_mutex(vm); + let mut mutex = Arc::new(Mutex::new(vm)); let ret_val = run_program(&mut mutex); #[cfg(feature = "count_insns")] diff --git a/vm/src/sys/audio.rs b/vm/src/sys/audio.rs index 183e1aa..26bb122 100644 --- a/vm/src/sys/audio.rs +++ b/vm/src/sys/audio.rs @@ -52,6 +52,9 @@ impl AudioCallback for AudioCB /// the Send trait, and so can't be referenced from another thread static mut DEVICE: Option> = None; + + +/* // NOTE: this can only be called from the main thread since it uses SDL // However, it creates a new thread to generate audio sample, this thread // could be given a reference to another VM instance @@ -104,3 +107,4 @@ pub fn audio_open_output(vm: &mut VM, sample_rate: Value, num_channels: Value, f // TODO: return the device_id (u32) Value::from(0) } +*/ diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index 0052672..dfe2b85 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -92,106 +92,36 @@ pub fn get_sdl_context() -> &'static mut sdl2::Sdl } } -pub struct SysState +/// Get the syscall with a given index +pub fn get_syscall(const_idx: u16) -> SysCallFn { - /// Map of indices to syscall functions - syscalls: [Option; SYSCALL_TBL_LEN], - - /// Weak reference to a mutex for the VM - mutex: Weak>, - - - -} - -impl SysState -{ - pub fn new() -> Self - { - let mut sys_state = Self { - syscalls: [None; SYSCALL_TBL_LEN], - mutex: Weak::new(), - //net_state: NetState::default(), - }; - - sys_state.init_syscalls(); - - sys_state - } - - pub fn get_mutex(vm: VM) -> Arc> - { - // Move the VM into a mutex - let vm_arc = Arc::new(Mutex::new(vm)); - - // Store a weak reference to the mutex into the sys state - vm_arc.lock().unwrap().sys_state.mutex = Arc::downgrade(&vm_arc); - - vm_arc - } - - /// Register a syscall implementation - pub fn reg_syscall(&mut self, const_idx: u16, fun: SysCallFn) - { - let desc = SYSCALL_DESCS[const_idx as usize].as_ref().unwrap(); - - assert!( - fun.argc() == desc.argc, - "{} should accept {} args but implementation has {} params", - desc.name, - desc.argc, - fun.argc() - ); - - assert!(fun.has_ret() == desc.has_ret); - - self.syscalls[const_idx as usize] = Some(fun); - } - - /// Get the syscall with a given index - pub fn get_syscall(&self, const_idx: u16) -> SysCallFn - { - if let Some(syscall_fn) = self.syscalls[const_idx as usize] { - return syscall_fn; - } - else - { - panic!("unknown syscall \"{}\"", const_idx); - } - } - - fn init_syscalls(&mut self) - { - let mut syscalls = HashMap::::new(); - + match const_idx { // Core VM syscalls - self.reg_syscall(VM_HEAP_SIZE, SysCallFn::Fn0_1(vm_heap_size)); - self.reg_syscall(VM_RESIZE_HEAP, SysCallFn::Fn1_1(vm_resize_heap)); - self.reg_syscall(MEMSET, SysCallFn::Fn3_0(memset)); - self.reg_syscall(MEMSET32, SysCallFn::Fn3_0(memset32)); - self.reg_syscall(MEMCPY, SysCallFn::Fn3_0(memcpy)); - self.reg_syscall(MEMCMP, SysCallFn::Fn3_1(memcmp)); - - self.reg_syscall(PRINT_I64, SysCallFn::Fn1_0(print_i64)); - self.reg_syscall(PRINT_F32, SysCallFn::Fn1_0(print_f32)); - self.reg_syscall(PRINT_STR, SysCallFn::Fn1_0(print_str)); - self.reg_syscall(PRINT_ENDL, SysCallFn::Fn0_0(print_endl)); - self.reg_syscall(PUTCHAR, SysCallFn::Fn1_1(putchar)); - self.reg_syscall(GETCHAR, SysCallFn::Fn0_1(getchar)); - + VM_HEAP_SIZE => SysCallFn::Fn0_1(vm_heap_size), + VM_RESIZE_HEAP => SysCallFn::Fn1_1(vm_resize_heap), + MEMSET => SysCallFn::Fn3_0(memset), + MEMSET32 => SysCallFn::Fn3_0(memset32), + MEMCPY => SysCallFn::Fn3_0(memcpy), + MEMCMP => SysCallFn::Fn3_1(memcmp), + + // Console I/O + PRINT_I64 => SysCallFn::Fn1_0(print_i64), + PRINT_F32 => SysCallFn::Fn1_0(print_f32), + PRINT_STR => SysCallFn::Fn1_0(print_str), + PRINT_ENDL => SysCallFn::Fn0_0(print_endl), + PUTCHAR => SysCallFn::Fn1_1(putchar), + GETCHAR => SysCallFn::Fn0_1(getchar), + + /* self.reg_syscall(TIME_CURRENT_MS, SysCallFn::Fn0_1(time_current_ms)); - //self.reg_syscall(TIME_DELAY_CB, SysCallFn::Fn2_0(time_delay_cb)); self.reg_syscall(WINDOW_CREATE, SysCallFn::Fn4_1(window_create)); self.reg_syscall(WINDOW_DRAW_FRAME, SysCallFn::Fn2_0(window_draw_frame)); self.reg_syscall(AUDIO_OPEN_OUTPUT, SysCallFn::Fn4_1(audio_open_output)); + */ - //self.reg_syscall(NET_LISTEN, SysCallFn::Fn2_1(net_listen)); - //self.reg_syscall(NET_ACCEPT, SysCallFn::Fn4_1(net_accept)); - //self.reg_syscall(NET_READ, SysCallFn::Fn3_1(net_read)); - //self.reg_syscall(NET_WRITE, SysCallFn::Fn3_1(net_write)); - //self.reg_syscall(NET_CLOSE, SysCallFn::Fn1_0(net_close)); + _ => panic!("unknown syscall \"{}\"", const_idx), } } diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs index 51c624e..15faad6 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/sys/window.rs @@ -12,7 +12,7 @@ use sdl2::pixels::PixelFormatEnum; use std::time::Duration; -use crate::sys::{SysState, get_sdl_context}; +use crate::sys::{get_sdl_context}; use crate::vm::{VM, Value}; /// SDL video subsystem diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 015db79..edc8b88 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -514,7 +514,7 @@ struct StackFrame pub struct Thread { // Thread id - pub tid: u64, + pub id: u64, // Parent VM pub vm: Arc>, @@ -526,6 +526,13 @@ pub struct Thread frames: Vec, } +impl Thread +{ + + + +} + @@ -534,9 +541,6 @@ pub struct Thread pub struct VM { - // Host system state - pub sys_state: SysState, - // Heap memory space heap: MemBlock, @@ -548,25 +552,17 @@ pub struct VM // List of stack frames (activation records) frames: Vec, - - // Count of executed instructions - //#[cfg(feature = "count_insns")] - //insn_count: u64, } impl VM { pub fn new(mut code: MemBlock, mut heap: MemBlock, syscalls: HashSet) -> Self { - // Initialize the system state - let sys_state = SysState::new(); - // Resize the code and heap space to a page size multiple code.resize(code.len()); heap.resize(heap.len()); Self { - sys_state, code, heap, stack: Vec::default(), @@ -1474,7 +1470,7 @@ impl VM Op::syscall => { let syscall_idx = self.code.read_pc::(&mut pc); - let syscall_fn = self.sys_state.get_syscall(syscall_idx); + let syscall_fn = get_syscall(syscall_idx); match syscall_fn { From db675597ca5d0874abd5884b58323f1aa11b418f Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 26 Aug 2024 16:13:06 -0400 Subject: [PATCH 05/80] Refactor syscalls to take thread ref instead of vm ref --- vm/src/sys/mod.rs | 74 ++++++++++++++++++++++++++++++++--------------- vm/src/vm.rs | 4 +++ 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index dfe2b85..3ea16e0 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -10,7 +10,7 @@ use std::io::Write; use std::io::Read; use std::io::{stdout, stdin}; use std::sync::{Arc, Weak, Mutex}; -use crate::vm::{Value, VM}; +use crate::vm::{Value, VM, Thread}; use window::*; use audio::*; use net::*; @@ -23,20 +23,20 @@ use constants::*; #[derive(Copy, Clone)] pub enum SysCallFn { - Fn0_0(fn(&mut VM)), - Fn0_1(fn(&mut VM) -> Value), + Fn0_0(fn(&mut Thread)), + Fn0_1(fn(&mut Thread) -> Value), - Fn1_0(fn(&mut VM, a0: Value)), - Fn1_1(fn(&mut VM, a0: Value) -> Value), + Fn1_0(fn(&mut Thread, a0: Value)), + Fn1_1(fn(&mut Thread, a0: Value) -> Value), - Fn2_0(fn(&mut VM, a0: Value, a1: Value)), - Fn2_1(fn(&mut VM, a0: Value, a1: Value) -> Value), + Fn2_0(fn(&mut Thread, a0: Value, a1: Value)), + Fn2_1(fn(&mut Thread, a0: Value, a1: Value) -> Value), - Fn3_0(fn(&mut VM, a0: Value, a1: Value, a2: Value)), - Fn3_1(fn(&mut VM, a0: Value, a1: Value, a2: Value) -> Value), + Fn3_0(fn(&mut Thread, a0: Value, a1: Value, a2: Value)), + Fn3_1(fn(&mut Thread, a0: Value, a1: Value, a2: Value) -> Value), - Fn4_0(fn(&mut VM, a0: Value, a1: Value, a2: Value, a3: Value)), - Fn4_1(fn(&mut VM, a0: Value, a1: Value, a2: Value, a3: Value) -> Value), + Fn4_0(fn(&mut Thread, a0: Value, a1: Value, a2: Value, a3: Value)), + Fn4_1(fn(&mut Thread, a0: Value, a1: Value, a2: Value, a3: Value) -> Value), } impl SysCallFn @@ -125,40 +125,57 @@ pub fn get_syscall(const_idx: u16) -> SysCallFn } } -fn vm_heap_size(vm: &mut VM) -> Value +fn vm_heap_size(thread: &mut Thread) -> Value { - Value::from(vm.heap_size()) + todo!(); + + //Value::from(vm.heap_size()) } -fn vm_resize_heap(vm: &mut VM, num_bytes: Value) -> Value +fn vm_resize_heap(thread: &mut Thread, num_bytes: Value) -> Value { + todo!(); + + /* let num_bytes = num_bytes.as_usize(); let new_size = vm.resize_heap(num_bytes); Value::from(new_size) + */ } -fn memset(vm: &mut VM, dst_ptr: Value, val: Value, num_bytes: Value) +fn memset(thread: &mut Thread, dst_ptr: Value, val: Value, num_bytes: Value) { + todo!(); + + /* let dst_ptr = dst_ptr.as_usize(); let val = val.as_u8(); let num_bytes = num_bytes.as_usize(); let mem_slice: &mut [u8] = vm.get_heap_slice(dst_ptr, num_bytes); mem_slice.fill(val); + */ } -fn memset32(vm: &mut VM, dst_ptr: Value, word: Value, num_words: Value) +fn memset32(thread: &mut Thread, dst_ptr: Value, word: Value, num_words: Value) { + todo!(); + + /* let dst_ptr = dst_ptr.as_usize(); let word = word.as_u32(); let num_words = num_words.as_usize(); let mem_slice: &mut [u32] = vm.get_heap_slice(dst_ptr, num_words); mem_slice.fill(word); + */ } -fn memcpy(vm: &mut VM, dst_ptr: Value, src_ptr: Value, num_bytes: Value) +fn memcpy(thread: &mut Thread, dst_ptr: Value, src_ptr: Value, num_bytes: Value) { + todo!(); + + /* let dst_ptr = dst_ptr.as_usize(); let src_ptr = src_ptr.as_usize(); let num_bytes = num_bytes.as_usize(); @@ -171,10 +188,14 @@ fn memcpy(vm: &mut VM, dst_ptr: Value, src_ptr: Value, num_bytes: Value) std::ptr::copy_nonoverlapping(src_ptr, dst_ptr, num_bytes); } + */ } -fn memcmp(vm: &mut VM, ptr_a: Value, ptr_b: Value, num_bytes: Value) -> Value +fn memcmp(thread: &mut Thread, ptr_a: Value, ptr_b: Value, num_bytes: Value) -> Value { + todo!(); + + /* let num_bytes = num_bytes.as_usize(); unsafe { @@ -185,36 +206,41 @@ fn memcmp(vm: &mut VM, ptr_a: Value, ptr_b: Value, num_bytes: Value) -> Value Value::from(result as u64) } + */ } -fn print_i64(vm: &mut VM, v: Value) +fn print_i64(thread: &mut Thread, v: Value) { let v = v.as_i64(); print!("{}", v); } -fn print_f32(vm: &mut VM, v: Value) +fn print_f32(thread: &mut Thread, v: Value) { let v = v.as_f32(); print!("{}", v); } /// Print a null-terminated UTF-8 string to stdout -fn print_str(vm: &mut VM, str_ptr: Value) +fn print_str(thread: &mut Thread, str_ptr: Value) { + todo!(); + + /* let rust_str = vm.get_heap_str(str_ptr.as_usize()); print!("{}", rust_str); + */ } /// Print a newline characted to stdout -fn print_endl(vm: &mut VM) +fn print_endl(thread: &mut Thread) { println!(); } /// Write one byte of input to stdout. /// Analogous to C's getchar -fn putchar(vm: &mut VM, byte: Value) -> Value +fn putchar(thread: &mut Thread, byte: Value) -> Value { let byte = byte.as_u8(); let bytes = byte.to_le_bytes(); @@ -227,7 +253,7 @@ fn putchar(vm: &mut VM, byte: Value) -> Value /// Read one byte of input from stdin. /// Analogous to C's getchar -fn getchar(vm: &mut VM) -> Value +fn getchar(thread: &mut Thread) -> Value { let ch = stdin().bytes().next(); diff --git a/vm/src/vm.rs b/vm/src/vm.rs index edc8b88..c4f448e 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -1472,6 +1472,9 @@ impl VM let syscall_idx = self.code.read_pc::(&mut pc); let syscall_fn = get_syscall(syscall_idx); + todo!(); + + /* match syscall_fn { SysCallFn::Fn0_0(fun) => { @@ -1539,6 +1542,7 @@ impl VM self.push(v); } } + */ } Op::exit => { From c762e8e3e96527013cea7b7e373bf9655474360a Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 27 Aug 2024 09:49:03 -0400 Subject: [PATCH 06/80] Sketch thread syscalls --- vm/src/sys/mod.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index 3ea16e0..01ecc20 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -104,6 +104,14 @@ pub fn get_syscall(const_idx: u16) -> SysCallFn MEMCPY => SysCallFn::Fn3_0(memcpy), MEMCMP => SysCallFn::Fn3_1(memcmp), + + //thread_id + //thread_sleep + //thread_spawn + //thread_join + + + // Console I/O PRINT_I64 => SysCallFn::Fn1_0(print_i64), PRINT_F32 => SysCallFn::Fn1_0(print_f32), @@ -132,6 +140,48 @@ fn vm_heap_size(thread: &mut Thread) -> Value //Value::from(vm.heap_size()) } +fn thread_id(thread: &mut Thread) -> Value +{ + Value::from(thread.id) +} + +// Make the current thread sleep +fn thread_sleep(thread: &mut Thread, msecs: Value) +{ + use std::thread; + use std::time::Duration; + let msecs = msecs.as_u64(); + thread::sleep(Duration::from_millis(msecs)); +} + +// Spawn a new thread +// Takes a function to call as argument +// Returns a thread id +fn thread_spawn(thread: &mut Thread, fun: Value) -> Value +{ + //let tid = VM::new_thread(&actor.vm, fun, vec![]); + //Value::from(tid) + + todo!(); +} + +// Wait for a thread to terminatr, produce the return value +fn thread_join(thread: &mut Thread, tid: Value) -> Value +{ + let tid = tid.as_u64(); + //VM::join_thread(&actor.vm, tid) + + todo!(); +} + + + + + + + + + fn vm_resize_heap(thread: &mut Thread, num_bytes: Value) -> Value { todo!(); From a2124413780a0c3370b65bd795f46a284d96cc8c Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Wed, 28 Aug 2024 11:03:32 -0400 Subject: [PATCH 07/80] Start refactoring VM impl --- vm/src/asm.rs | 4 +- vm/src/main.rs | 7 --- vm/src/sys/mod.rs | 40 ++++++---------- vm/src/vm.rs | 118 +++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 129 insertions(+), 40 deletions(-) diff --git a/vm/src/asm.rs b/vm/src/asm.rs index d634de9..7b9c79c 100644 --- a/vm/src/asm.rs +++ b/vm/src/asm.rs @@ -605,7 +605,9 @@ impl Assembler } } - Ok(VM::new(self.code, self.data, self.syscall_set)) + // FIXME: we need a concept of program or image separate from VM here + //Ok(VM::new(self.code, self.data, self.syscall_set)) + todo!(); } pub fn parse_file(mut self, file_name: &str) -> Result diff --git a/vm/src/main.rs b/vm/src/main.rs index 053ddb6..1aef4fe 100644 --- a/vm/src/main.rs +++ b/vm/src/main.rs @@ -152,12 +152,5 @@ fn main() let mut mutex = Arc::new(Mutex::new(vm)); let ret_val = run_program(&mut mutex); - #[cfg(feature = "count_insns")] - { - let mut vm = mutex.lock().unwrap(); - let insn_count = vm.get_insn_count(); - println!("insns executed: {}", thousands_sep(insn_count)); - } - exit(ret_val.as_i32()); } diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index 01ecc20..1df8623 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -140,6 +140,17 @@ fn vm_heap_size(thread: &mut Thread) -> Value //Value::from(vm.heap_size()) } +fn vm_resize_heap(thread: &mut Thread, num_bytes: Value) -> Value +{ + todo!(); + + /* + let num_bytes = num_bytes.as_usize(); + let new_size = vm.resize_heap(num_bytes); + Value::from(new_size) + */ +} + fn thread_id(thread: &mut Thread) -> Value { Value::from(thread.id) @@ -159,38 +170,15 @@ fn thread_sleep(thread: &mut Thread, msecs: Value) // Returns a thread id fn thread_spawn(thread: &mut Thread, fun: Value) -> Value { - //let tid = VM::new_thread(&actor.vm, fun, vec![]); - //Value::from(tid) - - todo!(); + let tid = VM::new_thread(&thread.vm, fun, vec![]); + Value::from(tid) } // Wait for a thread to terminatr, produce the return value fn thread_join(thread: &mut Thread, tid: Value) -> Value { let tid = tid.as_u64(); - //VM::join_thread(&actor.vm, tid) - - todo!(); -} - - - - - - - - - -fn vm_resize_heap(thread: &mut Thread, num_bytes: Value) -> Value -{ - todo!(); - - /* - let num_bytes = num_bytes.as_usize(); - let new_size = vm.resize_heap(num_bytes); - Value::from(new_size) - */ + VM::join_thread(&thread.vm, tid) } fn memset(thread: &mut Thread, dst_ptr: Value, val: Value, num_bytes: Value) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index c4f448e..32fcb5b 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -1,6 +1,7 @@ use std::sync::{Arc, Mutex}; use std::mem::{transmute, size_of}; -use std::collections::HashSet; +use std::collections::{HashSet, HashMap}; +use std::thread; use std::ffi::CStr; use crate::sys::*; @@ -552,24 +553,51 @@ pub struct VM // List of stack frames (activation records) frames: Vec, + + + + + // Next thread id to assign + next_tid: u64, + + // Map from actor ids to thread join handles + threads: HashMap>, + + // Reference to self + // Needed to instantiate actors + vm: Option>>, } +// Needed to send Arc> to thread +unsafe impl Send for VM {} + impl VM { - pub fn new(mut code: MemBlock, mut heap: MemBlock, syscalls: HashSet) -> Self + pub fn new(mut code: MemBlock, mut heap: MemBlock, syscalls: HashSet) -> Arc> { // Resize the code and heap space to a page size multiple code.resize(code.len()); heap.resize(heap.len()); - Self { + let vm = Self { code, heap, stack: Vec::default(), frames: Vec::default(), - #[cfg(feature = "count_insns")] - insn_count: 0, - } + + + next_tid: 0, + threads: HashMap::default(), + vm: None, + }; + + let vm = Arc::new(Mutex::new(vm)); + + // Store a reference to the mutex on the VM + // This is so we can pass this reference to threads + vm.lock().unwrap().vm = Some(vm.clone()); + + vm } #[cfg(feature = "count_insns")] @@ -1588,6 +1616,84 @@ impl VM } } } + + + + + + + // Create a new thread + pub fn new_thread(vm: &Arc>, fun: Value, args: Vec) -> u64 + { + /* + let vm_mutex = vm.clone(); + + // Assign an actor id + let mut vm_ref = vm.lock().unwrap(); + let actor_id = vm_ref.next_actor_id; + vm_ref.next_actor_id += 1; + drop(vm_ref); + + // Create a message queue for the actor + let (queue_tx, queue_rx) = mpsc::channel::(); + + // Spawn a new thread for the actor + let handle = thread::spawn(move || { + let mut actor = Actor::new(actor_id, vm_mutex, queue_rx); + actor.call(fun, &args) + }); + + // Store the join handles and queue endpoints on the VM + let mut vm_ref = vm.lock().unwrap(); + vm_ref.threads.insert(actor_id, handle); + vm_ref.actor_txs.insert(actor_id, queue_tx); + drop(vm_ref); + + tid + */ + + todo!(); + } + + // Wait for a thread to produce a result and return it + pub fn join_thread(vm: &Arc>, tid: u64) -> Value + { + /* + // Get the join handle, then release the VM lock + let mut vm = vm.lock().unwrap(); + let mut handle = vm.threads.remove(&tid).unwrap(); + drop(vm); + + // Note: there is no need to copy data when joining, + // because the actor sending the data is done running + handle.join().expect(&format!("could not actor thread with id {}", tid)) + */ + + todo!(); + } + + /* + // Call a function in the main actor + pub fn call(vm: &mut Arc>, fun: Value, args: Vec) -> Value + { + let vm_mutex = vm.clone(); + + // Create a message queue for the actor + let (queue_tx, queue_rx) = mpsc::channel::(); + + // Assign an actor id + // Store the queue endpoints on the VM + let mut vm_ref = vm.lock().unwrap(); + let actor_id = vm_ref.next_actor_id; + assert!(actor_id == 0); + vm_ref.next_actor_id += 1; + vm_ref.actor_txs.insert(actor_id, queue_tx); + drop(vm_ref); + + let mut actor = Actor::new(actor_id, vm_mutex, queue_rx); + actor.call(fun, &args) + } + */ } #[cfg(test)] From 4a3ed239c708469d11deeec9b1674f7a9f62b57d Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Thu, 29 Aug 2024 16:57:09 -0400 Subject: [PATCH 08/80] Move VM methods to Thread --- vm/src/sys/window.rs | 14 ++-- vm/src/vm.rs | 159 +++++++++++++++++++++---------------------- 2 files changed, 87 insertions(+), 86 deletions(-) diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs index 15faad6..dc2fe35 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/sys/window.rs @@ -13,7 +13,7 @@ use sdl2::pixels::PixelFormatEnum; use std::time::Duration; use crate::sys::{get_sdl_context}; -use crate::vm::{VM, Value}; +use crate::vm::{VM, Thread, Value}; /// SDL video subsystem /// This is a global variable because it doesn't implement @@ -63,7 +63,7 @@ fn get_window(window_id: u32) -> &'static mut Window<'static> } } -pub fn window_create(vm: &mut VM, width: Value, height: Value, title: Value, flags: Value) -> Value +pub fn window_create(thread: &mut Thread, width: Value, height: Value, title: Value, flags: Value) -> Value { unsafe { if WINDOW.is_some() { @@ -73,7 +73,7 @@ pub fn window_create(vm: &mut VM, width: Value, height: Value, title: Value, fla let width: u32 = width.as_usize().try_into().unwrap(); let height: u32 = height.as_usize().try_into().unwrap(); - let title_str = vm.get_heap_str(title.as_usize()).to_owned(); + let title_str = thread.get_heap_str(title.as_usize()).to_owned(); let video_subsystem = get_video_subsystem(); @@ -108,13 +108,17 @@ pub fn window_create(vm: &mut VM, width: Value, height: Value, title: Value, fla Value::from(0) } -pub fn window_draw_frame(vm: &mut VM, window_id: Value, src_addr: Value) +pub fn window_draw_frame(thread: &mut Thread, window_id: Value, src_addr: Value) { + + // TODO: test thread id + + let window = get_window(window_id.as_u32()); // Get the address to copy pixel data from let data_len = (4 * window.width * window.height) as usize; - let data_ptr = vm.get_heap_ptr(src_addr.as_usize(), data_len); + let data_ptr = thread.get_heap_ptr(src_addr.as_usize(), data_len); // If no frame has been drawn yet if window.texture.is_none() { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 32fcb5b..51c496f 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -532,79 +532,6 @@ impl Thread -} - - - - - - - -pub struct VM -{ - // Heap memory space - heap: MemBlock, - - // Code memory space - code: MemBlock, - - // Value stack - stack: Vec, - - // List of stack frames (activation records) - frames: Vec, - - - - - // Next thread id to assign - next_tid: u64, - - // Map from actor ids to thread join handles - threads: HashMap>, - - // Reference to self - // Needed to instantiate actors - vm: Option>>, -} - -// Needed to send Arc> to thread -unsafe impl Send for VM {} - -impl VM -{ - pub fn new(mut code: MemBlock, mut heap: MemBlock, syscalls: HashSet) -> Arc> - { - // Resize the code and heap space to a page size multiple - code.resize(code.len()); - heap.resize(heap.len()); - - let vm = Self { - code, - heap, - stack: Vec::default(), - frames: Vec::default(), - - - next_tid: 0, - threads: HashMap::default(), - vm: None, - }; - - let vm = Arc::new(Mutex::new(vm)); - - // Store a reference to the mutex on the VM - // This is so we can pass this reference to threads - vm.lock().unwrap().vm = Some(vm.clone()); - - vm - } - - #[cfg(feature = "count_insns")] - pub fn get_insn_count(&self) -> u64 - { - self.insn_count - } pub fn stack_size(&self) -> usize { @@ -627,20 +554,17 @@ impl VM /// Get the current size of the heap in bytes pub fn heap_size(&self) -> usize { - self.heap.len() - } + todo!(); - /// Resize the heap to a new size in bytes - pub fn resize_heap(&mut self, num_bytes: usize) -> usize - { - self.heap.resize(num_bytes) + //self.heap.len() } - // FIXME: this function should be marked unsafe - // /// Get a pointer to an address/offset in the heap pub fn get_heap_ptr(&mut self, addr: usize, num_elems: usize) -> *mut T { + todo!(); + + /* if addr + std::mem::size_of::() * num_elems > self.heap.len() { panic!("attempting to access memory slice past end of heap"); } @@ -656,11 +580,15 @@ impl VM let heap_ptr: *mut u8 = self.heap.data.as_mut_ptr().add(addr); transmute::<*mut u8 , *mut T>(heap_ptr) } + */ } /// Get a mutable slice to access a memory region in the heap pub fn get_heap_slice(&mut self, addr: usize, num_elems: usize) -> &mut [T] { + todo!(); + + /* if addr + std::mem::size_of::() * num_elems > self.heap.len() { panic!("attempting to access memory slice past end of heap"); } @@ -677,11 +605,15 @@ impl VM let start_ptr = transmute::<*mut u8 , *mut T>(heap_ptr); std::slice::from_raw_parts_mut(start_ptr, num_elems) } + */ } /// Copy an UTF-8 string at a given address in the heap pub fn get_heap_str(&mut self, str_ptr: usize) -> &str { + todo!(); + + /* // Verify that there is a null-terminator for this string // within the bounds of the heap let mut str_len = 0; @@ -704,6 +636,7 @@ impl VM let c_str = unsafe { CStr::from_ptr(char_ptr as *const i8) }; let rust_str = c_str.to_str().unwrap(); rust_str + */ } /// Call a function at a given address @@ -728,6 +661,7 @@ impl VM let mut bp = self.stack.len(); let mut pc = callee_pc as usize; + /* // For each instruction to execute loop { @@ -1615,13 +1549,76 @@ impl VM _ => panic!("unknown opcode {:?}", op), } } + */ + + todo!(); } +} + + + + +pub struct VM +{ + // Heap memory space + heap: MemBlock, + + // Code memory space + code: MemBlock, + + // Next thread id to assign + next_tid: u64, + + // Map from actor ids to thread join handles + threads: HashMap>, + + // Reference to self + // Needed to instantiate actors + vm: Option>>, +} + +// Needed to send Arc> to thread +unsafe impl Send for VM {} + +impl VM +{ + pub fn new(mut code: MemBlock, mut heap: MemBlock, syscalls: HashSet) -> Arc> + { + // Resize the code and heap space to a page size multiple + code.resize(code.len()); + heap.resize(heap.len()); + + let vm = Self { + code, + heap, + next_tid: 0, + threads: HashMap::default(), + vm: None, + }; + + let vm = Arc::new(Mutex::new(vm)); + + // Store a reference to the mutex on the VM + // This is so we can pass this reference to threads + vm.lock().unwrap().vm = Some(vm.clone()); + + vm + } + + /* + /// Resize the heap to a new size in bytes + pub fn resize_heap(&mut self, num_bytes: usize) -> usize + { + self.heap.resize(num_bytes) + } + */ + // Create a new thread pub fn new_thread(vm: &Arc>, fun: Value, args: Vec) -> u64 { From 28f21ca16f2ae0f485b489f512f472d01eca2778 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Thu, 29 Aug 2024 20:53:34 -0400 Subject: [PATCH 09/80] First pass at VM::call() --- vm/src/main.rs | 8 ++------ vm/src/vm.rs | 34 +++++++++++++++++----------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/vm/src/main.rs b/vm/src/main.rs index 1aef4fe..3dc88b5 100644 --- a/vm/src/main.rs +++ b/vm/src/main.rs @@ -69,13 +69,9 @@ fn parse_args(args: Vec) -> Options opts } -fn run_program(mutex: &mut Arc>) -> Value +fn run_program(vm: &mut Arc>) -> Value { - let mut vm = mutex.lock().unwrap(); - - let result = vm.call(0, &[]); - - drop(vm); + let result = VM::call(vm, 0, vec![]); /* loop diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 51c496f..dde8133 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -529,9 +529,15 @@ pub struct Thread impl Thread { - - - + pub fn new(tid: u64, vm: Arc>) -> Self + { + Self { + id: tid, + vm, + stack: Vec::default(), + frames: Vec::default(), + } + } pub fn stack_size(&self) -> usize { @@ -640,7 +646,7 @@ impl Thread } /// Call a function at a given address - pub fn call(&mut self, callee_pc: u64, args: &[Value]) -> Value + pub fn call(&mut self, callee_pc: u64, args: Vec) -> Value { assert!(self.stack.len() == 0); assert!(self.frames.len() == 0); @@ -654,7 +660,7 @@ impl Thread // Push the arguments on the stack for arg in args { - self.stack.push(*arg); + self.stack.push(arg); } // The base pointer will point at the first local @@ -1669,28 +1675,22 @@ impl VM todo!(); } - /* // Call a function in the main actor - pub fn call(vm: &mut Arc>, fun: Value, args: Vec) -> Value + pub fn call(vm: &mut Arc>, callee_pc: u64, args: Vec) -> Value { let vm_mutex = vm.clone(); - // Create a message queue for the actor - let (queue_tx, queue_rx) = mpsc::channel::(); - // Assign an actor id // Store the queue endpoints on the VM let mut vm_ref = vm.lock().unwrap(); - let actor_id = vm_ref.next_actor_id; - assert!(actor_id == 0); - vm_ref.next_actor_id += 1; - vm_ref.actor_txs.insert(actor_id, queue_tx); + let tid = vm_ref.next_tid; + assert!(tid == 0); + vm_ref.next_tid += 1; drop(vm_ref); - let mut actor = Actor::new(actor_id, vm_mutex, queue_rx); - actor.call(fun, &args) + let mut thread = Thread::new(tid, vm_mutex); + thread.call(callee_pc, args) } - */ } #[cfg(test)] From 7944ae1a78c245d90498a67d545a805cedae4594 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Thu, 29 Aug 2024 22:56:44 -0400 Subject: [PATCH 10/80] Implement Program and ByteArray structs --- vm/src/asm.rs | 41 ++++++---------- vm/src/main.rs | 63 +++--------------------- vm/src/program.rs | 121 +++++++++++++++++++++++++++++++++++++++++++++ vm/src/vm.rs | 123 ++++------------------------------------------ 4 files changed, 154 insertions(+), 194 deletions(-) create mode 100644 vm/src/program.rs diff --git a/vm/src/asm.rs b/vm/src/asm.rs index 7b9c79c..3224daa 100644 --- a/vm/src/asm.rs +++ b/vm/src/asm.rs @@ -3,7 +3,8 @@ use std::convert::{TryFrom}; use std::collections::HashMap; use std::collections::HashSet; use std::mem::transmute; -use crate::vm::{VM, MemBlock, Op}; +use crate::vm::{Op}; +use crate::program::*; #[derive(Debug)] pub struct ParseError @@ -485,10 +486,10 @@ pub struct Assembler syscall_set: HashSet, // Generated code - code: MemBlock, + code: ByteArray, // Data section - data: MemBlock, + data: ByteArray, /// Label definitions (name, position) label_defs: HashMap, @@ -504,18 +505,6 @@ impl Assembler { pub fn new() -> Self { - /* - // Populate the available constants - use crate::sys::constants::SYSCALL_DESCS; - let mut const_map = HashMap::new(); - for syscall in SYSCALL_DESCS { - const_map.insert( - syscall.name.to_uppercase(), - syscall.const_idx as i128 - ); - } - */ - /// Populate the available syscalls use crate::sys::constants::SYSCALL_DESCS; let mut syscall_map = HashMap::new(); @@ -529,15 +518,15 @@ impl Assembler const_map: HashMap::new(), syscall_map: syscall_map, syscall_set: HashSet::new(), - code: MemBlock::new(), - data: MemBlock::new(), + code: ByteArray::new(), + data: ByteArray::new(), label_defs: HashMap::default(), label_refs: Vec::default(), section: Section::Code, } } - fn parse_input(mut self, input: &mut Input) -> Result + fn parse_input(mut self, input: &mut Input) -> Result { // Until we've reached the end of the input loop @@ -605,12 +594,14 @@ impl Assembler } } - // FIXME: we need a concept of program or image separate from VM here - //Ok(VM::new(self.code, self.data, self.syscall_set)) - todo!(); + Ok(Program { + code: self.code, + data: self.data, + syscalls: self.syscall_set, + }) } - pub fn parse_file(mut self, file_name: &str) -> Result + pub fn parse_file(mut self, file_name: &str) -> Result { match std::fs::read_to_string(file_name) { Err(_) => { @@ -624,7 +615,7 @@ impl Assembler } /// Parse a string of source code - pub fn parse_str(mut self, src: &str) -> Result + pub fn parse_str(mut self, src: &str) -> Result { let mut input = Input::new(src.to_string()); return self.parse_input(&mut input); @@ -667,8 +658,8 @@ impl Assembler input.parse_error("expected integer argument") } - /// Get the memory block for the current section - fn mem(&mut self) -> &mut MemBlock + /// Get the byte array for the current section + fn mem(&mut self) -> &mut ByteArray { match self.section { Section::Code => &mut self.code, diff --git a/vm/src/main.rs b/vm/src/main.rs index 3dc88b5..bd230b9 100644 --- a/vm/src/main.rs +++ b/vm/src/main.rs @@ -6,6 +6,7 @@ mod vm; mod sys; mod asm; +mod program; mod utils; extern crate sdl2; @@ -15,7 +16,7 @@ use std::thread::sleep; use std::time::Duration; use std::process::exit; use std::sync::{Arc, Mutex}; -use crate::vm::{VM, Value, MemBlock}; +use crate::vm::{VM, Value}; use crate::asm::{Assembler}; use crate::utils::{thousands_sep}; @@ -69,56 +70,6 @@ fn parse_args(args: Vec) -> Options opts } -fn run_program(vm: &mut Arc>) -> Value -{ - let result = VM::call(vm, 0, vec![]); - - /* - loop - { - let mut vm = mutex.lock().unwrap(); - - if let ExitReason::Exit(val) = sys::window::process_events(&mut vm) { - return val; - } - - let next_cb_time = sys::time::time_until_next_cb(&mut vm); - - // Unlock the VM mutex before going to sleep, so that other threads, - // such as the audio thread, may use the VM - drop(vm); - - // Sleep until the next callback - if let Some(delay_ms) = next_cb_time { - let min_delay = std::cmp::min(delay_ms, 10); - sleep(Duration::from_millis(min_delay)); - } - else - { - sleep(Duration::from_millis(10)); - } - - let mut vm = mutex.lock().unwrap(); - - // For each callback to run - for pc in sys::time::get_cbs_to_run(&mut vm) - { - match vm.call(pc, &[]) - { - ExitReason::Exit(val) => { - return val; - } - ExitReason::Return(val) => { - } - } - } - } - */ - - - todo!(); -} - fn main() { let opts = parse_args(env::args().collect()); @@ -132,9 +83,9 @@ fn main() // Parse/compile the program let asm = Assembler::new(); - let result = asm.parse_file(file_name); + let program = asm.parse_file(file_name); - if let Err(error) = &result { + if let Err(error) = program { println!("Error: {}", error); exit(-1); } @@ -144,9 +95,9 @@ fn main() exit(0); } - let vm = result.unwrap(); - let mut mutex = Arc::new(Mutex::new(vm)); - let ret_val = run_program(&mut mutex); + let program = program.unwrap(); + let mut vm = VM::new(program); + let ret_val = VM::call(&mut vm, 0, vec![]); exit(ret_val.as_i32()); } diff --git a/vm/src/program.rs b/vm/src/program.rs new file mode 100644 index 0000000..2a8e459 --- /dev/null +++ b/vm/src/program.rs @@ -0,0 +1,121 @@ +use std::collections::HashSet; +use std::mem::transmute; +use crate::vm::Op; + +pub struct ByteArray +{ + data: Vec +} + +impl ByteArray +{ + pub fn new() -> Self + { + Self { + data: Vec::default() + } + } + + /// Get the memory block size in bytes + pub fn len(&self) -> usize + { + self.data.len() + } + + pub fn push_op(&mut self, op: Op) + { + self.data.push(op as u8); + } + + pub fn push_u8(&mut self, val: u8) + { + self.data.push(val); + } + + pub fn push_u16(&mut self, val: u16) + { + for byte in val.to_le_bytes() { + self.data.push(byte); + } + } + + pub fn push_i8(&mut self, val: i8) + { + self.data.push(val as u8); + } + + pub fn push_i32(&mut self, val: i32) + { + for byte in val.to_le_bytes() { + self.data.push(byte); + } + } + + pub fn push_u32(&mut self, val: u32) + { + for byte in val.to_le_bytes() { + self.data.push(byte); + } + } + + pub fn push_u64(&mut self, val: u64) + { + for byte in val.to_le_bytes() { + self.data.push(byte); + } + } + + /// Write a value at the given address + pub fn write(&mut self, pos: usize, val: T) where T: Copy + { + unsafe { + let buf_ptr = self.data.as_mut_ptr(); + let val_ptr = transmute::<*mut u8 , *mut T>(buf_ptr.add(pos)); + std::ptr::write_unaligned(val_ptr, val); + } + } + + /* + /// Resize to a new size in bytes + pub fn resize(&mut self, mut num_bytes: usize) -> usize + { + // Round up to a page size multiple + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize; + assert!(page_size % 8 == 0); + let rem = num_bytes % page_size; + if rem != 0 { + num_bytes += page_size - rem; + } + + assert!(num_bytes % page_size == 0); + self.data.resize(num_bytes, 0); + + num_bytes + } + */ + + /* + /// Read a value at the current PC and then increment the PC + pub fn read_pc(&self, pc: &mut usize) -> T where T: Copy + { + unsafe { + let buf_ptr = self.data.as_ptr(); + let val_ptr = transmute::<*const u8 , *const T>(buf_ptr.add(*pc)); + *pc += size_of::(); + std::ptr::read_unaligned(val_ptr) + } + } + */ +} + +pub struct Program +{ + // Executable code + pub code: ByteArray, + + // Data segment + pub data: ByteArray, + + // Set of syscalls referenced by this program + pub syscalls: HashSet, +} diff --git a/vm/src/vm.rs b/vm/src/vm.rs index dde8133..c402cd7 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -4,6 +4,7 @@ use std::collections::{HashSet, HashMap}; use std::thread; use std::ffi::CStr; use crate::sys::*; +use crate::program::Program; /// Instruction opcodes /// Note: commonly used upcodes should be in the [0, 127] range (one byte) @@ -394,108 +395,6 @@ impl From for Value { } } -pub struct MemBlock -{ - data: Vec -} - -impl MemBlock -{ - pub fn new() -> Self - { - Self { - data: Vec::default() - } - } - - /// Get the memory block size in bytes - pub fn len(&self) -> usize - { - self.data.len() - } - - /// Resize to a new size in bytes - pub fn resize(&mut self, mut num_bytes: usize) -> usize - { - // Round up to a page size multiple - let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize; - assert!(page_size % 8 == 0); - let rem = num_bytes % page_size; - if rem != 0 { - num_bytes += page_size - rem; - } - - assert!(num_bytes % page_size == 0); - self.data.resize(num_bytes, 0); - - num_bytes - } - - pub fn push_op(&mut self, op: Op) - { - self.data.push(op as u8); - } - - pub fn push_u8(&mut self, val: u8) - { - self.data.push(val); - } - - pub fn push_u16(&mut self, val: u16) - { - for byte in val.to_le_bytes() { - self.data.push(byte); - } - } - - pub fn push_i8(&mut self, val: i8) - { - self.data.push(val as u8); - } - - pub fn push_i32(&mut self, val: i32) - { - for byte in val.to_le_bytes() { - self.data.push(byte); - } - } - - pub fn push_u32(&mut self, val: u32) - { - for byte in val.to_le_bytes() { - self.data.push(byte); - } - } - - pub fn push_u64(&mut self, val: u64) - { - for byte in val.to_le_bytes() { - self.data.push(byte); - } - } - - /// Write a value at the given address - pub fn write(&mut self, pos: usize, val: T) where T: Copy - { - unsafe { - let buf_ptr = self.data.as_mut_ptr(); - let val_ptr = transmute::<*mut u8 , *mut T>(buf_ptr.add(pos)); - std::ptr::write_unaligned(val_ptr, val); - } - } - - /// Read a value at the current PC and then increment the PC - pub fn read_pc(&self, pc: &mut usize) -> T where T: Copy - { - unsafe { - let buf_ptr = self.data.as_ptr(); - let val_ptr = transmute::<*const u8 , *const T>(buf_ptr.add(*pc)); - *pc += size_of::(); - std::ptr::read_unaligned(val_ptr) - } - } -} - struct StackFrame { // Previous base pointer at the time of call @@ -671,10 +570,12 @@ impl Thread // For each instruction to execute loop { + /* #[cfg(feature = "count_insns")] { self.insn_count += 1; } + */ if pc >= self.code.len() { panic!("pc outside bounds of code space") @@ -1559,9 +1460,6 @@ impl Thread todo!(); } - - - } @@ -1573,10 +1471,10 @@ impl Thread pub struct VM { // Heap memory space - heap: MemBlock, + //heap: MemBlock, // Code memory space - code: MemBlock, + //code: MemBlock, // Next thread id to assign next_tid: u64, @@ -1594,15 +1492,14 @@ unsafe impl Send for VM {} impl VM { - pub fn new(mut code: MemBlock, mut heap: MemBlock, syscalls: HashSet) -> Arc> + pub fn new(prog: Program) -> Arc> { - // Resize the code and heap space to a page size multiple - code.resize(code.len()); - heap.resize(heap.len()); + + let vm = Self { - code, - heap, + //code, + //heap, next_tid: 0, threads: HashMap::default(), vm: None, From daf8f63e811b7555d1c4b596f5358beab34fda7d Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Fri, 30 Aug 2024 09:52:21 -0400 Subject: [PATCH 11/80] Start working on MemBlock --- vm/src/program.rs | 32 -------------- vm/src/vm.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 32 deletions(-) diff --git a/vm/src/program.rs b/vm/src/program.rs index 2a8e459..e90bb67 100644 --- a/vm/src/program.rs +++ b/vm/src/program.rs @@ -74,38 +74,6 @@ impl ByteArray std::ptr::write_unaligned(val_ptr, val); } } - - /* - /// Resize to a new size in bytes - pub fn resize(&mut self, mut num_bytes: usize) -> usize - { - // Round up to a page size multiple - let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize; - assert!(page_size % 8 == 0); - let rem = num_bytes % page_size; - if rem != 0 { - num_bytes += page_size - rem; - } - - assert!(num_bytes % page_size == 0); - self.data.resize(num_bytes, 0); - - num_bytes - } - */ - - /* - /// Read a value at the current PC and then increment the PC - pub fn read_pc(&self, pc: &mut usize) -> T where T: Copy - { - unsafe { - let buf_ptr = self.data.as_ptr(); - let val_ptr = transmute::<*const u8 , *const T>(buf_ptr.add(*pc)); - *pc += size_of::(); - std::ptr::read_unaligned(val_ptr) - } - } - */ } pub struct Program diff --git a/vm/src/vm.rs b/vm/src/vm.rs index c402cd7..b04dd9a 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -411,6 +411,114 @@ struct StackFrame + +struct MemBlock +{ + // Underlying memory block + mem_block: *mut u8, + + // Total size of the mapped memory block + mapping_size: usize, + + // System page size + page_size: usize, + + // Currently accessible size + size_bytes: usize, +} + +impl MemBlock +{ + pub fn new() -> MemBlock + { + // Try to allocate a very large block first (512GB) + let start_size: usize = 512 * 1024 * 1024 * 1024; + + let mut alloc_size = start_size; + + let mut mem_block; + + // Try to allocate a contiguous block of memory that is + // as large as possible + loop { + // PROT_NONE means the data cannot be accessed yet + mem_block = unsafe {libc::mmap( + std::ptr::null_mut(), + alloc_size, + libc::PROT_NONE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0 + )}; + + if mem_block != libc::MAP_FAILED { + println!("mmap successful {}", alloc_size); + break; + } + + // Try again with a smaller alloc size + alloc_size /= 2; + } + + assert!(alloc_size >= 1024); + + MemBlock { + mem_block: unsafe { transmute(mem_block) }, + mapping_size: alloc_size, + page_size: unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize, + size_bytes: 0, + } + } + + + + + /* + /// Resize to a new size in bytes + pub fn resize(&mut self, mut num_bytes: usize) -> usize + { + // Round up to a page size multiple + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize; + assert!(page_size % 8 == 0); + let rem = num_bytes % page_size; + if rem != 0 { + num_bytes += page_size - rem; + } + + assert!(num_bytes % page_size == 0); + self.data.resize(num_bytes, 0); + + num_bytes + } + */ + + + + + // NOTE: this will need to be a macro on threads + /* + /// Read a value at the current PC and then increment the PC + pub fn read_pc(&self, pc: &mut usize) -> T where T: Copy + { + unsafe { + let buf_ptr = self.data.as_ptr(); + let val_ptr = transmute::<*const u8 , *const T>(buf_ptr.add(*pc)); + *pc += size_of::(); + std::ptr::read_unaligned(val_ptr) + } + } + */ + + +} + + + + + + + + pub struct Thread { // Thread id From 257b2521c9b2850a36c17f36a859af47046fad7a Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Fri, 30 Aug 2024 13:28:08 -0400 Subject: [PATCH 12/80] First pass at MemBlock::resize() --- vm/src/vm.rs | 56 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index b04dd9a..02d62c7 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -423,8 +423,8 @@ struct MemBlock // System page size page_size: usize, - // Currently accessible size - size_bytes: usize, + // Currently visible/accessible size + cur_size: usize, } impl MemBlock @@ -462,35 +462,61 @@ impl MemBlock assert!(alloc_size >= 1024); + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize; + assert!(page_size % 8 == 0); + MemBlock { mem_block: unsafe { transmute(mem_block) }, mapping_size: alloc_size, - page_size: unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize, - size_bytes: 0, + page_size, + cur_size: 0, } } - - /* /// Resize to a new size in bytes - pub fn resize(&mut self, mut num_bytes: usize) -> usize + pub fn resize(&mut self, mut new_size: usize) -> usize { // Round up to a page size multiple - let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize; - assert!(page_size % 8 == 0); - let rem = num_bytes % page_size; + let rem = new_size % self.page_size; if rem != 0 { - num_bytes += page_size - rem; + new_size += self.page_size - rem; + } + assert!(new_size % self.page_size == 0); + + // Growing the memory block, need to map as read | write + if new_size > self.cur_size { + let start_ofs = self.cur_size; + let map_addr = unsafe { transmute(self.mem_block.add(start_ofs)) }; + + let map_size = new_size - self.cur_size; + assert!(map_size % self.page_size == 0); + + let mem_block = unsafe {libc::mmap( + map_addr, + map_size, + libc::PROT_WRITE | libc::PROT_READ, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0 + )}; + + if mem_block == libc::MAP_FAILED { + panic!(); + } + } else { + todo!(); } - assert!(num_bytes % page_size == 0); - self.data.resize(num_bytes, 0); - num_bytes + // TODO: update accessible size + + + + //new_size + todo!(); } - */ From d9e51a866247903404a4659a21efaeb5402e9526 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 14:17:06 -0400 Subject: [PATCH 13/80] WIP grow heap logic, MemView --- vm/src/vm.rs | 78 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 02d62c7..2d1416c 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -473,10 +473,12 @@ impl MemBlock } } - - - /// Resize to a new size in bytes - pub fn resize(&mut self, mut new_size: usize) -> usize + /// Grow to a new size in bytes + /// This operation is a no-op if the existing size + /// is greater or equal to the requested size + /// + /// Note: this operation must be guarded by the VM + pub fn grow(&mut self, mut new_size: usize) -> usize { // Round up to a page size multiple let rem = new_size % self.page_size; @@ -486,42 +488,63 @@ impl MemBlock assert!(new_size % self.page_size == 0); // Growing the memory block, need to map as read | write - if new_size > self.cur_size { - let start_ofs = self.cur_size; - let map_addr = unsafe { transmute(self.mem_block.add(start_ofs)) }; + if new_size <= self.cur_size { + return self.cur_size; + } - let map_size = new_size - self.cur_size; - assert!(map_size % self.page_size == 0); + let start_ofs = self.cur_size; + let map_addr = unsafe { transmute(self.mem_block.add(start_ofs)) }; - let mem_block = unsafe {libc::mmap( - map_addr, - map_size, - libc::PROT_WRITE | libc::PROT_READ, - libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, - -1, - 0 - )}; + let map_size = new_size - self.cur_size; + assert!(map_size % self.page_size == 0); - if mem_block == libc::MAP_FAILED { - panic!(); - } - } else { - todo!(); + let mem_block = unsafe {libc::mmap( + map_addr, + map_size, + libc::PROT_WRITE | libc::PROT_READ, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0 + )}; + + if mem_block == libc::MAP_FAILED { + panic!(); } + // Update the currently accessible size + self.cur_size = new_size; - // TODO: update accessible size + new_size + } + // TODO: method to create MemView - //new_size - todo!(); - } - // NOTE: this will need to be a macro on threads +} + + +struct MemView +{ + // Underlying memory block + mem_block: *mut u8, + + // TODO: pointer to atomic var + + +} + +impl MemView +{ + + + + // TODO: + + /* /// Read a value at the current PC and then increment the PC pub fn read_pc(&self, pc: &mut usize) -> T where T: Copy @@ -535,7 +558,6 @@ impl MemBlock } */ - } From ffa755010b03f29447d8a774841cc42780288f84 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 15:11:51 -0400 Subject: [PATCH 14/80] Change vm_resize_heap into vm_grow_heap Makes intention more explicit. Safer for concurrent malloc. --- api/syscalls.json | 4 ++-- doc/syscalls.md | 6 +++--- ncc/include/uvm/syscalls.h | 6 +++--- vm/src/sys/constants.rs | 4 ++-- vm/src/sys/mod.rs | 12 ++++-------- vm/src/vm.rs | 10 +++++----- 6 files changed, 19 insertions(+), 23 deletions(-) diff --git a/api/syscalls.json b/api/syscalls.json index e7bab75..036506b 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -111,7 +111,7 @@ "description": "Report the current heap size in bytes." }, { - "name": "vm_resize_heap", + "name": "vm_grow_heap", "args": [ [ "u64", @@ -124,7 +124,7 @@ ], "permission": "default_allowed", "const_idx": 17, - "description": "Resize the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. Returns the new heap size in bytes if successful, or `UINT64_MAX` on failure." + "description": "Grow the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. If the requested size is smaller than the current heap size, this is a no-op. Returns the new heap size in bytes." } ], "constants": [] diff --git a/doc/syscalls.md b/doc/syscalls.md index 780cb35..71ab8cb 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -58,15 +58,15 @@ u64 vm_heap_size() Report the current heap size in bytes. -## vm_resize_heap +## vm_grow_heap ``` -u64 vm_resize_heap(u64 num_bytes) +u64 vm_grow_heap(u64 num_bytes) ``` **Returns:** `u64 new_size` -Resize the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. Returns the new heap size in bytes if successful, or `UINT64_MAX` on failure. +Grow the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. If the requested size is smaller than the current heap size, this is a no-op. Returns the new heap size in bytes. # io diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index 8276da4..38b7250 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -25,9 +25,9 @@ // Report the current heap size in bytes. #define vm_heap_size() asm () -> u64 { syscall vm_heap_size; } -// u64 vm_resize_heap(u64 num_bytes) -// Resize the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. Returns the new heap size in bytes if successful, or `UINT64_MAX` on failure. -#define vm_resize_heap(__num_bytes) asm (__num_bytes) -> u64 { syscall vm_resize_heap; } +// u64 vm_grow_heap(u64 num_bytes) +// Grow the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. If the requested size is smaller than the current heap size, this is a no-op. Returns the new heap size in bytes. +#define vm_grow_heap(__num_bytes) asm (__num_bytes) -> u64 { syscall vm_grow_heap; } // void print_i64(i64 val) // Print an i64 value to standard output. diff --git a/vm/src/sys/constants.rs b/vm/src/sys/constants.rs index b21b764..32aed49 100644 --- a/vm/src/sys/constants.rs +++ b/vm/src/sys/constants.rs @@ -23,7 +23,7 @@ pub const WINDOW_ON_MOUSEUP: u16 = 13; pub const VM_HEAP_SIZE: u16 = 14; pub const WINDOW_ON_KEYUP: u16 = 15; pub const MEMSET32: u16 = 16; -pub const VM_RESIZE_HEAP: u16 = 17; +pub const VM_GROW_HEAP: u16 = 17; pub const AUDIO_OPEN_OUTPUT: u16 = 18; pub const WINDOW_ON_TEXTINPUT: u16 = 19; pub const PRINT_F32: u16 = 20; @@ -61,7 +61,7 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ Some(SysCallDesc { name: "vm_heap_size", const_idx: 14, argc: 0, has_ret: true }), Some(SysCallDesc { name: "window_on_keyup", const_idx: 15, argc: 2, has_ret: false }), Some(SysCallDesc { name: "memset32", const_idx: 16, argc: 3, has_ret: false }), - Some(SysCallDesc { name: "vm_resize_heap", const_idx: 17, argc: 1, has_ret: true }), + Some(SysCallDesc { name: "vm_grow_heap", const_idx: 17, argc: 1, has_ret: true }), Some(SysCallDesc { name: "audio_open_output", const_idx: 18, argc: 4, has_ret: true }), Some(SysCallDesc { name: "window_on_textinput", const_idx: 19, argc: 2, has_ret: false }), Some(SysCallDesc { name: "print_f32", const_idx: 20, argc: 1, has_ret: false }), diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index 1df8623..bd18f82 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -98,13 +98,12 @@ pub fn get_syscall(const_idx: u16) -> SysCallFn match const_idx { // Core VM syscalls VM_HEAP_SIZE => SysCallFn::Fn0_1(vm_heap_size), - VM_RESIZE_HEAP => SysCallFn::Fn1_1(vm_resize_heap), + VM_GROW_HEAP => SysCallFn::Fn1_1(vm_grow_heap), MEMSET => SysCallFn::Fn3_0(memset), MEMSET32 => SysCallFn::Fn3_0(memset32), MEMCPY => SysCallFn::Fn3_0(memcpy), MEMCMP => SysCallFn::Fn3_1(memcmp), - //thread_id //thread_sleep //thread_spawn @@ -140,15 +139,12 @@ fn vm_heap_size(thread: &mut Thread) -> Value //Value::from(vm.heap_size()) } -fn vm_resize_heap(thread: &mut Thread, num_bytes: Value) -> Value +fn vm_grow_heap(thread: &mut Thread, num_bytes: Value) -> Value { - todo!(); - - /* + let mut vm = thread.vm.lock().unwrap(); let num_bytes = num_bytes.as_usize(); - let new_size = vm.resize_heap(num_bytes); + let new_size = vm.grow_heap(num_bytes); Value::from(new_size) - */ } fn thread_id(thread: &mut Thread) -> Value diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 2d1416c..30beaf7 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -1670,13 +1670,13 @@ impl VM vm } - /* - /// Resize the heap to a new size in bytes - pub fn resize_heap(&mut self, num_bytes: usize) -> usize + /// Grow the heap to a new size in bytes + pub fn grow_heap(&mut self, num_bytes: usize) -> usize { - self.heap.resize(num_bytes) + todo!(); + + //self.heap.resize(num_bytes) } - */ // Create a new thread pub fn new_thread(vm: &Arc>, fun: Value, args: Vec) -> u64 From 821ede9568a7adda583e6fe86be227e491b40880 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 15:22:44 -0400 Subject: [PATCH 15/80] Box the memory block size --- vm/src/vm.rs | 55 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 30beaf7..44842d4 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -424,7 +424,9 @@ struct MemBlock page_size: usize, // Currently visible/accessible size - cur_size: usize, + // This is a box because we need a pointer + // To access this value from threads using MemView + cur_size: Box, } impl MemBlock @@ -469,7 +471,7 @@ impl MemBlock mem_block: unsafe { transmute(mem_block) }, mapping_size: alloc_size, page_size, - cur_size: 0, + cur_size: Box::new(0), } } @@ -487,15 +489,16 @@ impl MemBlock } assert!(new_size % self.page_size == 0); + let cur_size = *self.cur_size; + // Growing the memory block, need to map as read | write - if new_size <= self.cur_size { - return self.cur_size; + if new_size <= cur_size { + return cur_size; } - let start_ofs = self.cur_size; - let map_addr = unsafe { transmute(self.mem_block.add(start_ofs)) }; + let map_addr = unsafe { transmute(self.mem_block.add(cur_size)) }; - let map_size = new_size - self.cur_size; + let map_size = new_size - cur_size; assert!(map_size % self.page_size == 0); let mem_block = unsafe {libc::mmap( @@ -512,29 +515,28 @@ impl MemBlock } // Update the currently accessible size - self.cur_size = new_size; + *self.cur_size = new_size; new_size } - - // TODO: method to create MemView - - - - - + // Create a new thread-local view on this memory block + fn new_view(&self) -> MemView + { + MemView { + mem_block: self.mem_block, + cur_size: &*self.cur_size, + } + } } - struct MemView { // Underlying memory block mem_block: *mut u8, - // TODO: pointer to atomic var - - + // Pointer to size variable from parent MemBlock + cur_size: *const usize, } impl MemView @@ -542,12 +544,23 @@ impl MemView - // TODO: + + /* + /// Write a value at the given address + pub fn write(&mut self, pos: usize, val: T) where T: Copy + { + unsafe { + let buf_ptr = self.data.as_mut_ptr(); + let val_ptr = transmute::<*mut u8 , *mut T>(buf_ptr.add(pos)); + std::ptr::write_unaligned(val_ptr, val); + } + } + */ /* /// Read a value at the current PC and then increment the PC - pub fn read_pc(&self, pc: &mut usize) -> T where T: Copy + pub fn read_at_pc(&self, pc: &mut usize) -> T where T: Copy { unsafe { let buf_ptr = self.data.as_ptr(); From de57749072b121e027c96b9ea5b48887024d0523 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 15:57:56 -0400 Subject: [PATCH 16/80] Implement MemView methods --- vm/src/vm.rs | 57 ++++++++++++++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 44842d4..128fd03 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -541,45 +541,45 @@ struct MemView impl MemView { + pub fn size_bytes(&self) -> usize + { + unsafe { *self.cur_size } + } + /// Get a mutable pointer to an address/offset + pub fn get_ptr_mut(&mut self, addr: usize, num_elems: usize) -> *mut T + { + // Check that the address is within bounds + let cur_size = unsafe { *self.cur_size }; + if addr + std::mem::size_of::() * num_elems > cur_size { + panic!("attempting to access memory slice past end of heap"); + } + // Check that the address is aligned + if addr & (size_of::() - 1) != 0 { + panic!( + "attempting to access data of type {} at unaligned address", + std::any::type_name::() + ); + } - - /* - /// Write a value at the given address - pub fn write(&mut self, pos: usize, val: T) where T: Copy - { unsafe { - let buf_ptr = self.data.as_mut_ptr(); - let val_ptr = transmute::<*mut u8 , *mut T>(buf_ptr.add(pos)); - std::ptr::write_unaligned(val_ptr, val); + let ptr: *mut u8 = self.mem_block.add(addr); + transmute::<*mut u8 , *mut T>(ptr) } } - */ - - /* /// Read a value at the current PC and then increment the PC pub fn read_at_pc(&self, pc: &mut usize) -> T where T: Copy { unsafe { - let buf_ptr = self.data.as_ptr(); - let val_ptr = transmute::<*const u8 , *const T>(buf_ptr.add(*pc)); + let val_ptr = transmute::<*const u8 , *const T>(self.mem_block.add(*pc)); *pc += size_of::(); std::ptr::read_unaligned(val_ptr) } } - */ - } - - - - - - - pub struct Thread { // Thread id @@ -1640,10 +1640,10 @@ impl Thread pub struct VM { // Heap memory space - //heap: MemBlock, + heap: MemBlock, // Code memory space - //code: MemBlock, + code: MemBlock, // Next thread id to assign next_tid: u64, @@ -1663,12 +1663,17 @@ impl VM { pub fn new(prog: Program) -> Arc> { + let code = MemBlock::new(); + let heap = MemBlock::new(); + + // TODO: resize code and heap memblock, copy program data + let vm = Self { - //code, - //heap, + code, + heap, next_tid: 0, threads: HashMap::default(), vm: None, From 9c221566b71a0a20eddd1738df7b45fbcacd7443 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 16:41:53 -0400 Subject: [PATCH 17/80] Copy code and data into MemBlocks --- vm/src/program.rs | 5 +++++ vm/src/vm.rs | 47 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/vm/src/program.rs b/vm/src/program.rs index e90bb67..4b3a748 100644 --- a/vm/src/program.rs +++ b/vm/src/program.rs @@ -16,6 +16,11 @@ impl ByteArray } } + pub fn as_slice(&self) -> &[u8] + { + &self.data[..] + } + /// Get the memory block size in bytes pub fn len(&self) -> usize { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 128fd03..f7c2227 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -569,6 +569,30 @@ impl MemView } } + /// Get a mutable slice inside this memory block + pub fn get_slice_mut(&mut self, addr: usize, num_elems: usize) -> &mut [T] + { + // Check that the slice is within bounds + let cur_size = unsafe { *self.cur_size }; + if addr + std::mem::size_of::() * num_elems > cur_size { + panic!("attempting to access memory slice past end of heap"); + } + + // Check that the address is aligned + if addr & (size_of::() - 1) != 0 { + panic!( + "attempting to access unaligned memory slice of type {}", + std::any::type_name::() + ); + } + + unsafe { + let heap_ptr: *mut u8 = self.mem_block.add(addr); + let start_ptr = transmute::<*mut u8 , *mut T>(heap_ptr); + std::slice::from_raw_parts_mut(start_ptr, num_elems) + } + } + /// Read a value at the current PC and then increment the PC pub fn read_at_pc(&self, pc: &mut usize) -> T where T: Copy { @@ -1663,13 +1687,22 @@ impl VM { pub fn new(prog: Program) -> Arc> { - let code = MemBlock::new(); - let heap = MemBlock::new(); - - // TODO: resize code and heap memblock, copy program data - - - + let mut code = MemBlock::new(); + let mut heap = MemBlock::new(); + + // Resize the code and memory blocks to accomodate the program + code.grow(prog.code.len()); + heap.grow(prog.data.len()); + + // Copy the program code + let mut code_view = code.new_view(); + let mut code_slice: &mut [u8] = code_view.get_slice_mut(0, prog.code.len()); + code_slice.clone_from_slice(prog.code.as_slice()); + + // Copy the program data + let mut heap_view = heap.new_view(); + let mut heap_slice: &mut [u8] = heap_view.get_slice_mut(0, prog.data.len()); + heap_slice.clone_from_slice(prog.data.as_slice()); let vm = Self { code, From d073329e45c1c3ea1b1902cf2322ec7496fb31c7 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 16:56:53 -0400 Subject: [PATCH 18/80] Give each thread MemViews --- vm/src/sys/mod.rs | 7 ++--- vm/src/vm.rs | 70 +++++++++++++++++++++++++++-------------------- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index bd18f82..465e74c 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -134,9 +134,7 @@ pub fn get_syscall(const_idx: u16) -> SysCallFn fn vm_heap_size(thread: &mut Thread) -> Value { - todo!(); - - //Value::from(vm.heap_size()) + Value::from(thread.heap_size()) } fn vm_grow_heap(thread: &mut Thread, num_bytes: Value) -> Value @@ -166,7 +164,8 @@ fn thread_sleep(thread: &mut Thread, msecs: Value) // Returns a thread id fn thread_spawn(thread: &mut Thread, fun: Value) -> Value { - let tid = VM::new_thread(&thread.vm, fun, vec![]); + let callee_pc = fun.as_u64(); + let tid = VM::new_thread(&thread.vm, callee_pc, vec![]); Value::from(tid) } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index f7c2227..97a4875 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -539,6 +539,8 @@ struct MemView cur_size: *const usize, } +unsafe impl Send for MemView {} + impl MemView { pub fn size_bytes(&self) -> usize @@ -612,6 +614,12 @@ pub struct Thread // Parent VM pub vm: Arc>, + // Code memory block + code: MemView, + + // Heap memory block + heap: MemView, + // Value stack stack: Vec, @@ -621,11 +629,13 @@ pub struct Thread impl Thread { - pub fn new(tid: u64, vm: Arc>) -> Self + fn new(tid: u64, vm: Arc>, code: MemView, heap: MemView) -> Self { Self { id: tid, vm, + code, + heap, stack: Vec::default(), frames: Vec::default(), } @@ -652,9 +662,7 @@ impl Thread /// Get the current size of the heap in bytes pub fn heap_size(&self) -> usize { - todo!(); - - //self.heap.len() + self.heap.size_bytes() } /// Get a pointer to an address/offset in the heap @@ -1655,12 +1663,6 @@ impl Thread } } - - - - - - pub struct VM { // Heap memory space @@ -1730,36 +1732,41 @@ impl VM } // Create a new thread - pub fn new_thread(vm: &Arc>, fun: Value, args: Vec) -> u64 + pub fn new_thread(vm: &Arc>, callee_pc: u64, args: Vec) -> u64 { - /* - let vm_mutex = vm.clone(); - - // Assign an actor id + // Assign a thread id let mut vm_ref = vm.lock().unwrap(); - let actor_id = vm_ref.next_actor_id; - vm_ref.next_actor_id += 1; + let tid = vm_ref.next_tid; + vm_ref.next_tid += 1; + + // Create thread-local code and heap memory block views + let code = vm_ref.code.new_view(); + let heap = vm_ref.heap.new_view(); + drop(vm_ref); - // Create a message queue for the actor - let (queue_tx, queue_rx) = mpsc::channel::(); + let vm_mutex = vm.clone(); - // Spawn a new thread for the actor + // Spawn a new thread let handle = thread::spawn(move || { - let mut actor = Actor::new(actor_id, vm_mutex, queue_rx); - actor.call(fun, &args) + let mut thread = Thread::new(tid, vm_mutex, code, heap); + thread.call(callee_pc, args) }); + + + // TODO + /* // Store the join handles and queue endpoints on the VM let mut vm_ref = vm.lock().unwrap(); vm_ref.threads.insert(actor_id, handle); vm_ref.actor_txs.insert(actor_id, queue_tx); drop(vm_ref); - - tid */ - todo!(); + + + tid } // Wait for a thread to produce a result and return it @@ -1782,17 +1789,20 @@ impl VM // Call a function in the main actor pub fn call(vm: &mut Arc>, callee_pc: u64, args: Vec) -> Value { - let vm_mutex = vm.clone(); - - // Assign an actor id - // Store the queue endpoints on the VM + // Assign a thread id let mut vm_ref = vm.lock().unwrap(); let tid = vm_ref.next_tid; assert!(tid == 0); vm_ref.next_tid += 1; + + // Create thread-local code and heap memory block views + let code = vm_ref.code.new_view(); + let heap = vm_ref.heap.new_view(); + drop(vm_ref); - let mut thread = Thread::new(tid, vm_mutex); + let vm_mutex = vm.clone(); + let mut thread = Thread::new(tid, vm_mutex, code, heap); thread.call(callee_pc, args) } } From 231697d01df77bb7807ba21a764ff95c45c6a691 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 17:01:23 -0400 Subject: [PATCH 19/80] Uncomment interpreter loop. Fix PC bounds check. --- vm/src/vm.rs | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 97a4875..f2a2e18 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -596,8 +596,15 @@ impl MemView } /// Read a value at the current PC and then increment the PC - pub fn read_at_pc(&self, pc: &mut usize) -> T where T: Copy + pub fn read_pc(&self, pc: &mut usize) -> T where T: Copy { + // Check that the address is within bounds + let cur_size = unsafe { *self.cur_size }; + if *pc + std::mem::size_of::() > cur_size { + // TODO: output name of type being read + panic!("pc outside of bounds of code space"); + } + unsafe { let val_ptr = transmute::<*const u8 , *const T>(self.mem_block.add(*pc)); *pc += size_of::(); @@ -767,21 +774,9 @@ impl Thread let mut bp = self.stack.len(); let mut pc = callee_pc as usize; - /* // For each instruction to execute loop { - /* - #[cfg(feature = "count_insns")] - { - self.insn_count += 1; - } - */ - - if pc >= self.code.len() { - panic!("pc outside bounds of code space") - } - let op = self.code.read_pc::(&mut pc); //dbg!(op); @@ -1657,9 +1652,6 @@ impl Thread _ => panic!("unknown opcode {:?}", op), } } - */ - - todo!(); } } From ac32915b7a657fa91ee1bcc3cd6b40927ae4e03a Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 17:32:47 -0400 Subject: [PATCH 20/80] Working towards test execution --- vm/src/main.rs | 2 +- vm/src/vm.rs | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/vm/src/main.rs b/vm/src/main.rs index bd230b9..598402e 100644 --- a/vm/src/main.rs +++ b/vm/src/main.rs @@ -97,7 +97,7 @@ fn main() let program = program.unwrap(); let mut vm = VM::new(program); - let ret_val = VM::call(&mut vm, 0, vec![]); + let ret_val = VM::call(&mut vm, 0, &[]); exit(ret_val.as_i32()); } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index f2a2e18..eef1188 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -458,8 +458,11 @@ impl MemBlock break; } + println!("mmap failed, trying again"); + // Try again with a smaller alloc size alloc_size /= 2; + assert!(alloc_size > 1); } assert!(alloc_size >= 1024); @@ -753,7 +756,7 @@ impl Thread } /// Call a function at a given address - pub fn call(&mut self, callee_pc: u64, args: Vec) -> Value + pub fn call(&mut self, callee_pc: u64, args: &[Value]) -> Value { assert!(self.stack.len() == 0); assert!(self.frames.len() == 0); @@ -767,7 +770,7 @@ impl Thread // Push the arguments on the stack for arg in args { - self.stack.push(arg); + self.stack.push(*arg); } // The base pointer will point at the first local @@ -1742,7 +1745,7 @@ impl VM // Spawn a new thread let handle = thread::spawn(move || { let mut thread = Thread::new(tid, vm_mutex, code, heap); - thread.call(callee_pc, args) + thread.call(callee_pc, args.as_slice()) }); @@ -1779,7 +1782,7 @@ impl VM } // Call a function in the main actor - pub fn call(vm: &mut Arc>, callee_pc: u64, args: Vec) -> Value + pub fn call(vm: &mut Arc>, callee_pc: u64, args: &[Value]) -> Value { // Assign a thread id let mut vm_ref = vm.lock().unwrap(); @@ -1809,9 +1812,9 @@ mod tests { dbg!(src); let asm = Assembler::new(); - let mut vm = asm.parse_str(src).unwrap(); - let result = vm.call(0, &[]); - assert!(vm.stack.len() == 0 && vm.frames.len() == 0); + let prog = asm.parse_str(src).unwrap(); + let mut vm = VM::new(prog); + let result = VM::call(&mut vm, 0, &[]); result } From d086a3704a7cfef4a17a05e074a4aaaa23e8ca9d Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 17:49:11 -0400 Subject: [PATCH 21/80] Fix bug in mmap logic --- vm/src/vm.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index eef1188..6903e96 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -499,6 +499,7 @@ impl MemBlock return cur_size; } + // Compute the address from which to mmap let map_addr = unsafe { transmute(self.mem_block.add(cur_size)) }; let map_size = new_size - cur_size; @@ -508,7 +509,7 @@ impl MemBlock map_addr, map_size, libc::PROT_WRITE | libc::PROT_READ, - libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_FIXED, -1, 0 )}; From 22f264d288080d8606a8e1535dbf690a3912f44a Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 17:56:16 -0400 Subject: [PATCH 22/80] Get test_load_store working --- vm/src/sys/window.rs | 12 +++++++---- vm/src/vm.rs | 49 ++++++++++++-------------------------------- 2 files changed, 21 insertions(+), 40 deletions(-) diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs index dc2fe35..d46e90a 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/sys/window.rs @@ -65,6 +65,10 @@ fn get_window(window_id: u32) -> &'static mut Window<'static> pub fn window_create(thread: &mut Thread, width: Value, height: Value, title: Value, flags: Value) -> Value { + if thread.id != 0 { + panic!("window functions should only be called from the main thread"); + } + unsafe { if WINDOW.is_some() { panic!("for now, only one window supported"); @@ -110,15 +114,15 @@ pub fn window_create(thread: &mut Thread, width: Value, height: Value, title: Va pub fn window_draw_frame(thread: &mut Thread, window_id: Value, src_addr: Value) { - - // TODO: test thread id - + if thread.id != 0 { + panic!("window functions should only be called from the main thread"); + } let window = get_window(window_id.as_u32()); // Get the address to copy pixel data from let data_len = (4 * window.width * window.height) as usize; - let data_ptr = thread.get_heap_ptr(src_addr.as_usize(), data_len); + let data_ptr = thread.get_heap_ptr_mut(src_addr.as_usize(), data_len); // If no frame has been drawn yet if window.texture.is_none() { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 6903e96..bd97606 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -652,11 +652,6 @@ impl Thread } } - pub fn stack_size(&self) -> usize - { - self.stack.len() - } - pub fn push(&mut self, val: T) where Value: From { self.stack.push(Value::from(val)); @@ -676,28 +671,10 @@ impl Thread self.heap.size_bytes() } - /// Get a pointer to an address/offset in the heap - pub fn get_heap_ptr(&mut self, addr: usize, num_elems: usize) -> *mut T + /// Get a mutable pointer to an address/offset in the heap + pub fn get_heap_ptr_mut(&mut self, addr: usize, num_elems: usize) -> *mut T { - todo!(); - - /* - if addr + std::mem::size_of::() * num_elems > self.heap.len() { - panic!("attempting to access memory slice past end of heap"); - } - - if addr & (size_of::() - 1) != 0 { - panic!( - "attempting to access data of type {} at unaligned address", - std::any::type_name::() - ); - } - - unsafe { - let heap_ptr: *mut u8 = self.heap.data.as_mut_ptr().add(addr); - transmute::<*mut u8 , *mut T>(heap_ptr) - } - */ + self.heap.get_ptr_mut(addr, num_elems) } /// Get a mutable slice to access a memory region in the heap @@ -725,7 +702,7 @@ impl Thread */ } - /// Copy an UTF-8 string at a given address in the heap + /// Read an UTF-8 string at a given address in the heap into a Rust string pub fn get_heap_str(&mut self, str_ptr: usize) -> &str { todo!(); @@ -749,7 +726,7 @@ impl Thread } // Convert the string to a Rust string - let char_ptr = self.get_heap_ptr(str_ptr, str_len); + let char_ptr = self.get_heap_ptr_mut(str_ptr, str_len); let c_str = unsafe { CStr::from_ptr(char_ptr as *const i8) }; let rust_str = c_str.to_str().unwrap(); rust_str @@ -1420,28 +1397,28 @@ impl Thread Op::load_u8 => { let addr = self.pop().as_usize(); - let heap_ptr = self.get_heap_ptr(addr, 1); + let heap_ptr = self.get_heap_ptr_mut(addr, 1); let val: u8 = unsafe { *heap_ptr }; self.push(val); } Op::load_u16 => { let addr = self.pop().as_usize(); - let heap_ptr = self.get_heap_ptr(addr, 1); + let heap_ptr = self.get_heap_ptr_mut(addr, 1); let val: u16 = unsafe { *heap_ptr }; self.push(val); } Op::load_u32 => { let addr = self.pop().as_usize(); - let heap_ptr = self.get_heap_ptr(addr, 1); + let heap_ptr = self.get_heap_ptr_mut(addr, 1); let val: u32 = unsafe { *heap_ptr }; self.push(val); } Op::load_u64 => { let addr = self.pop().as_usize(); - let heap_ptr = self.get_heap_ptr(addr, 1); + let heap_ptr = self.get_heap_ptr_mut(addr, 1); let val: u64 = unsafe { *heap_ptr }; self.push(val); } @@ -1449,28 +1426,28 @@ impl Thread Op::store_u8 => { let val = self.pop().as_u8(); let addr = self.pop().as_usize(); - let heap_ptr = self.get_heap_ptr(addr, 1); + let heap_ptr = self.get_heap_ptr_mut(addr, 1); unsafe { *heap_ptr = val; } } Op::store_u16 => { let val = self.pop().as_u16(); let addr = self.pop().as_usize(); - let heap_ptr = self.get_heap_ptr(addr, 1); + let heap_ptr = self.get_heap_ptr_mut(addr, 1); unsafe { *heap_ptr = val; } } Op::store_u32 => { let val = self.pop().as_u32(); let addr = self.pop().as_usize(); - let heap_ptr = self.get_heap_ptr(addr, 1); + let heap_ptr = self.get_heap_ptr_mut(addr, 1); unsafe { *heap_ptr = val; } } Op::store_u64 => { let val = self.pop().as_u64(); let addr = self.pop().as_usize(); - let heap_ptr = self.get_heap_ptr(addr, 1); + let heap_ptr = self.get_heap_ptr_mut(addr, 1); unsafe { *heap_ptr = val; } } From 3ea3b063602cb7ce434a62398837bebb38a6e1cb Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 19:05:02 -0400 Subject: [PATCH 23/80] Get memset, simple syscalls working --- vm/src/sys/mod.rs | 6 +----- vm/src/vm.rs | 27 ++------------------------- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index 465e74c..84cfce5 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -178,16 +178,12 @@ fn thread_join(thread: &mut Thread, tid: Value) -> Value fn memset(thread: &mut Thread, dst_ptr: Value, val: Value, num_bytes: Value) { - todo!(); - - /* let dst_ptr = dst_ptr.as_usize(); let val = val.as_u8(); let num_bytes = num_bytes.as_usize(); - let mem_slice: &mut [u8] = vm.get_heap_slice(dst_ptr, num_bytes); + let mem_slice: &mut [u8] = thread.get_heap_slice_mut(dst_ptr, num_bytes); mem_slice.fill(val); - */ } fn memset32(thread: &mut Thread, dst_ptr: Value, word: Value, num_words: Value) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index bd97606..f9ce677 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -678,28 +678,9 @@ impl Thread } /// Get a mutable slice to access a memory region in the heap - pub fn get_heap_slice(&mut self, addr: usize, num_elems: usize) -> &mut [T] + pub fn get_heap_slice_mut(&mut self, addr: usize, num_elems: usize) -> &mut [T] { - todo!(); - - /* - if addr + std::mem::size_of::() * num_elems > self.heap.len() { - panic!("attempting to access memory slice past end of heap"); - } - - if addr & (size_of::() - 1) != 0 { - panic!( - "attempting to access unaligned memory slice of type {}", - std::any::type_name::() - ); - } - - unsafe { - let heap_ptr: *mut u8 = self.heap.data.as_mut_ptr().add(addr); - let start_ptr = transmute::<*mut u8 , *mut T>(heap_ptr); - std::slice::from_raw_parts_mut(start_ptr, num_elems) - } - */ + self.heap.get_slice_mut(addr, num_elems) } /// Read an UTF-8 string at a given address in the heap into a Rust string @@ -1518,9 +1499,6 @@ impl Thread let syscall_idx = self.code.read_pc::(&mut pc); let syscall_fn = get_syscall(syscall_idx); - todo!(); - - /* match syscall_fn { SysCallFn::Fn0_0(fun) => { @@ -1588,7 +1566,6 @@ impl Thread self.push(v); } } - */ } Op::exit => { From bd4e9bec63bbd3bceb528f33acb58b472e745a65 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 19:15:51 -0400 Subject: [PATCH 24/80] Get print_str syscall working --- vm/src/sys/mod.rs | 6 +----- vm/src/vm.rs | 35 ++++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index 84cfce5..49e459e 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -253,12 +253,8 @@ fn print_f32(thread: &mut Thread, v: Value) /// Print a null-terminated UTF-8 string to stdout fn print_str(thread: &mut Thread, str_ptr: Value) { - todo!(); - - /* - let rust_str = vm.get_heap_str(str_ptr.as_usize()); + let rust_str = thread.get_heap_str(str_ptr.as_usize()); print!("{}", rust_str); - */ } /// Print a newline characted to stdout diff --git a/vm/src/vm.rs b/vm/src/vm.rs index f9ce677..3433a03 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -575,10 +575,10 @@ impl MemView } } - /// Get a mutable slice inside this memory block - pub fn get_slice_mut(&mut self, addr: usize, num_elems: usize) -> &mut [T] + /// Get a constant pointer to an address/offset + pub fn get_ptr(&self, addr: usize, num_elems: usize) -> *const T { - // Check that the slice is within bounds + // Check that the address is within bounds let cur_size = unsafe { *self.cur_size }; if addr + std::mem::size_of::() * num_elems > cur_size { panic!("attempting to access memory slice past end of heap"); @@ -587,14 +587,22 @@ impl MemView // Check that the address is aligned if addr & (size_of::() - 1) != 0 { panic!( - "attempting to access unaligned memory slice of type {}", + "attempting to access data of type {} at unaligned address", std::any::type_name::() ); } unsafe { - let heap_ptr: *mut u8 = self.mem_block.add(addr); - let start_ptr = transmute::<*mut u8 , *mut T>(heap_ptr); + let ptr: *mut u8 = self.mem_block.add(addr); + transmute::<*mut u8 , *const T>(ptr) + } + } + + /// Get a mutable slice inside this memory block + pub fn get_slice_mut(&mut self, addr: usize, num_elems: usize) -> &mut [T] + { + unsafe { + let start_ptr = self.get_ptr_mut(addr, num_elems); std::slice::from_raw_parts_mut(start_ptr, num_elems) } } @@ -684,22 +692,20 @@ impl Thread } /// Read an UTF-8 string at a given address in the heap into a Rust string - pub fn get_heap_str(&mut self, str_ptr: usize) -> &str + pub fn get_heap_str(&self, str_ptr: usize) -> &str { - todo!(); - - /* // Verify that there is a null-terminator for this string // within the bounds of the heap let mut str_len = 0; loop { - let char_idx = str_ptr + str_len; - if char_idx >= self.heap.len() { + let char_ptr = str_ptr + str_len; + if char_ptr >= self.heap.size_bytes() { panic!("string is not properly null-terminated"); } - if self.heap.data[char_idx] == 0 { + let byte_ptr: *const u8 = self.heap.get_ptr(char_ptr, 1); + if unsafe { *byte_ptr } == 0 { break; } @@ -707,11 +713,10 @@ impl Thread } // Convert the string to a Rust string - let char_ptr = self.get_heap_ptr_mut(str_ptr, str_len); + let char_ptr = self.heap.get_ptr(str_ptr, str_len); let c_str = unsafe { CStr::from_ptr(char_ptr as *const i8) }; let rust_str = c_str.to_str().unwrap(); rust_str - */ } /// Call a function at a given address From bc863b892cc8de6779cf9a0434c0c8e85598eabb Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 20:00:33 -0400 Subject: [PATCH 25/80] Add thread syscalls and time_current_ms --- api/syscalls.json | 59 ++++++++++++++++++++++++++++++++++++++ doc/syscalls.md | 36 +++++++++++++++++++++++ ncc/include/uvm/syscalls.h | 16 +++++++++++ vm/src/sys/constants.rs | 10 ++++++- vm/src/sys/mod.rs | 20 ++++++------- vm/src/sys/time.rs | 4 +-- vm/src/vm.rs | 21 ++------------ 7 files changed, 133 insertions(+), 33 deletions(-) diff --git a/api/syscalls.json b/api/syscalls.json index 036506b..837a095 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -125,6 +125,65 @@ "permission": "default_allowed", "const_idx": 17, "description": "Grow the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. If the requested size is smaller than the current heap size, this is a no-op. Returns the new heap size in bytes." + }, + { + "name": "thread_spawn", + "args": [ + [ + "void*", + "fptr" + ] + ], + "returns": [ + "u64", + "tid" + ], + "permission": "default_allowed", + "const_idx": 29, + "description": "Spawn a new thread running the given function." + }, + { + "name": "thread_id", + "args": [], + "returns": [ + "u64", + "tid" + ], + "permission": "default_allowed", + "const_idx": 28, + "description": "Get the id of the current thread." + }, + { + "name": "thread_sleep", + "args": [ + [ + "u64", + "time_ms" + ] + ], + "returns": [ + "void", + "" + ], + "permission": "default_allowed", + "const_idx": 30, + "description": "Make the current thread sleep for at least the given time in milliseconds." + }, + { + "name": "thread_join", + "args": [ + [ + "u64", + "tid" + ] + ], + "returns": [ + "u64", + "val" + ], + "permission": "default_allowed", + "const_idx": 31, + "description": "Join on the thread with the given id. Produces the return value for the thread." } ], "constants": [] diff --git a/doc/syscalls.md b/doc/syscalls.md index 71ab8cb..21cc181 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -68,6 +68,42 @@ u64 vm_grow_heap(u64 num_bytes) Grow the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. If the requested size is smaller than the current heap size, this is a no-op. Returns the new heap size in bytes. +## thread_spawn + +``` +u64 thread_spawn(void* fptr) +``` + +**Returns:** `u64 tid` + +Spawn a new thread running the given function. + +## thread_id + +``` +u64 thread_id() +``` + +**Returns:** `u64 tid` + +Get the id of the current thread. + +## thread_sleep + +``` +void thread_sleep(u64 time_ms) +``` + +Make the current thread sleep for at least the given time in milliseconds. + +## thread_join + +``` +void thread_join(u64 tid) +``` + +Join on the thread with the given id. + # io Stream I/O functionality. diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index 38b7250..88b1bb1 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -29,6 +29,22 @@ // Grow the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. If the requested size is smaller than the current heap size, this is a no-op. Returns the new heap size in bytes. #define vm_grow_heap(__num_bytes) asm (__num_bytes) -> u64 { syscall vm_grow_heap; } +// u64 thread_spawn(void* fptr) +// Spawn a new thread running the given function. +#define thread_spawn(__fptr) asm (__fptr) -> u64 { syscall thread_spawn; } + +// u64 thread_id() +// Get the id of the current thread. +#define thread_id() asm () -> u64 { syscall thread_id; } + +// void thread_sleep(u64 time_ms) +// Make the current thread sleep for at least the given time in milliseconds. +#define thread_sleep(__time_ms) asm (__time_ms) -> void { syscall thread_sleep; } + +// void thread_join(u64 tid) +// Join on the thread with the given id. +#define thread_join(__tid) asm (__tid) -> void { syscall thread_join; } + // void print_i64(i64 val) // Print an i64 value to standard output. #define print_i64(__val) asm (__val) -> void { syscall print_i64; } diff --git a/vm/src/sys/constants.rs b/vm/src/sys/constants.rs index 32aed49..4a0b106 100644 --- a/vm/src/sys/constants.rs +++ b/vm/src/sys/constants.rs @@ -4,7 +4,7 @@ #![allow(unused)] -pub const SYSCALL_TBL_LEN: usize = 28; +pub const SYSCALL_TBL_LEN: usize = 32; pub const TIME_CURRENT_MS: u16 = 0; pub const WINDOW_CREATE: u16 = 1; @@ -34,6 +34,10 @@ pub const NET_WRITE: u16 = 24; pub const NET_CLOSE: u16 = 25; pub const PUTCHAR: u16 = 26; pub const MEMCMP: u16 = 27; +pub const THREAD_ID: u16 = 28; +pub const THREAD_SPAWN: u16 = 29; +pub const THREAD_SLEEP: u16 = 30; +pub const THREAD_JOIN: u16 = 31; pub struct SysCallDesc { @@ -72,6 +76,10 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ Some(SysCallDesc { name: "net_close", const_idx: 25, argc: 1, has_ret: false }), Some(SysCallDesc { name: "putchar", const_idx: 26, argc: 1, has_ret: true }), Some(SysCallDesc { name: "memcmp", const_idx: 27, argc: 3, has_ret: true }), + Some(SysCallDesc { name: "thread_id", const_idx: 28, argc: 0, has_ret: true }), + Some(SysCallDesc { name: "thread_spawn", const_idx: 29, argc: 1, has_ret: true }), + Some(SysCallDesc { name: "thread_sleep", const_idx: 30, argc: 1, has_ret: false }), + Some(SysCallDesc { name: "thread_join", const_idx: 31, argc: 1, has_ret: false }), ]; pub const KEY_BACKSPACE: u16 = 8; diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index 49e459e..510a410 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -104,12 +104,10 @@ pub fn get_syscall(const_idx: u16) -> SysCallFn MEMCPY => SysCallFn::Fn3_0(memcpy), MEMCMP => SysCallFn::Fn3_1(memcmp), - //thread_id - //thread_sleep - //thread_spawn - //thread_join - - + THREAD_SPAWN => SysCallFn::Fn1_1(thread_spawn), + THREAD_JOIN => SysCallFn::Fn1_1(thread_join), + THREAD_ID => SysCallFn::Fn0_1(thread_id), + THREAD_SLEEP => SysCallFn::Fn1_0(thread_sleep), // Console I/O PRINT_I64 => SysCallFn::Fn1_0(print_i64), @@ -119,14 +117,12 @@ pub fn get_syscall(const_idx: u16) -> SysCallFn PUTCHAR => SysCallFn::Fn1_1(putchar), GETCHAR => SysCallFn::Fn0_1(getchar), - /* - self.reg_syscall(TIME_CURRENT_MS, SysCallFn::Fn0_1(time_current_ms)); + TIME_CURRENT_MS => SysCallFn::Fn0_1(time_current_ms), - self.reg_syscall(WINDOW_CREATE, SysCallFn::Fn4_1(window_create)); - self.reg_syscall(WINDOW_DRAW_FRAME, SysCallFn::Fn2_0(window_draw_frame)); + //WINDOW_CREATE => SysCallFn::Fn4_1(window_create), + //WINDOW_DRAW_FRAME => SysCallFn::Fn2_0(window_draw_frame), - self.reg_syscall(AUDIO_OPEN_OUTPUT, SysCallFn::Fn4_1(audio_open_output)); - */ + //AUDIO_OPEN_OUTPUT => SysCallFn::Fn4_1(audio_open_output), _ => panic!("unknown syscall \"{}\"", const_idx), } diff --git a/vm/src/sys/time.rs b/vm/src/sys/time.rs index 0140313..0e72ddc 100644 --- a/vm/src/sys/time.rs +++ b/vm/src/sys/time.rs @@ -1,5 +1,5 @@ use std::time::{SystemTime, UNIX_EPOCH}; -use crate::vm::{VM, Value}; +use crate::vm::{Thread, Value}; /// Get the current time stamp in milliseconds pub fn get_time_ms() -> u64 @@ -8,7 +8,7 @@ pub fn get_time_ms() -> u64 } /// Get the current time stamp in milliseconds since the unix epoch -pub fn time_current_ms(vm: &mut VM) -> Value +pub fn time_current_ms(thread: &mut Thread) -> Value { Value::from(get_time_ms()) } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 3433a03..75c9e9e 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -454,7 +454,6 @@ impl MemBlock )}; if mem_block != libc::MAP_FAILED { - println!("mmap successful {}", alloc_size); break; } @@ -1708,18 +1707,10 @@ impl VM thread.call(callee_pc, args.as_slice()) }); - - - // TODO - /* - // Store the join handles and queue endpoints on the VM + // Store the join handle on the VM let mut vm_ref = vm.lock().unwrap(); - vm_ref.threads.insert(actor_id, handle); - vm_ref.actor_txs.insert(actor_id, queue_tx); + vm_ref.threads.insert(tid, handle); drop(vm_ref); - */ - - tid } @@ -1727,18 +1718,12 @@ impl VM // Wait for a thread to produce a result and return it pub fn join_thread(vm: &Arc>, tid: u64) -> Value { - /* // Get the join handle, then release the VM lock let mut vm = vm.lock().unwrap(); let mut handle = vm.threads.remove(&tid).unwrap(); drop(vm); - // Note: there is no need to copy data when joining, - // because the actor sending the data is done running - handle.join().expect(&format!("could not actor thread with id {}", tid)) - */ - - todo!(); + handle.join().expect(&format!("could join thread with id {}", tid)) } // Call a function in the main actor From 2e5f729a289af2a74da931d85ac935ceed1d5708 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 21:08:39 -0400 Subject: [PATCH 26/80] Add simple thread test program --- doc/syscalls.md | 6 ++++-- ncc/include/uvm/syscalls.h | 6 +++--- ncc/tests/threads.c | 17 +++++++++++++++++ vm/src/sys/constants.rs | 2 +- 4 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 ncc/tests/threads.c diff --git a/doc/syscalls.md b/doc/syscalls.md index 21cc181..04e55da 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -99,10 +99,12 @@ Make the current thread sleep for at least the given time in milliseconds. ## thread_join ``` -void thread_join(u64 tid) +u64 thread_join(u64 tid) ``` -Join on the thread with the given id. +**Returns:** `u64 val` + +Join on the thread with the given id. Produces the return value for the thread. # io diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index 88b1bb1..5690afb 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -41,9 +41,9 @@ // Make the current thread sleep for at least the given time in milliseconds. #define thread_sleep(__time_ms) asm (__time_ms) -> void { syscall thread_sleep; } -// void thread_join(u64 tid) -// Join on the thread with the given id. -#define thread_join(__tid) asm (__tid) -> void { syscall thread_join; } +// u64 thread_join(u64 tid) +// Join on the thread with the given id. Produces the return value for the thread. +#define thread_join(__tid) asm (__tid) -> u64 { syscall thread_join; } // void print_i64(i64 val) // Print an i64 value to standard output. diff --git a/ncc/tests/threads.c b/ncc/tests/threads.c new file mode 100644 index 0000000..a771774 --- /dev/null +++ b/ncc/tests/threads.c @@ -0,0 +1,17 @@ +#include +#include + +u64 thread_fn() +{ + thread_sleep(5); + return 7; +} + +int main() +{ + u64 tid = thread_spawn(thread_fn); + u64 ret = thread_join(tid); + assert(ret == 7); + + return 0; +} diff --git a/vm/src/sys/constants.rs b/vm/src/sys/constants.rs index 4a0b106..3939986 100644 --- a/vm/src/sys/constants.rs +++ b/vm/src/sys/constants.rs @@ -79,7 +79,7 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ Some(SysCallDesc { name: "thread_id", const_idx: 28, argc: 0, has_ret: true }), Some(SysCallDesc { name: "thread_spawn", const_idx: 29, argc: 1, has_ret: true }), Some(SysCallDesc { name: "thread_sleep", const_idx: 30, argc: 1, has_ret: false }), - Some(SysCallDesc { name: "thread_join", const_idx: 31, argc: 1, has_ret: false }), + Some(SysCallDesc { name: "thread_join", const_idx: 31, argc: 1, has_ret: true }), ]; pub const KEY_BACKSPACE: u16 = 8; From d620438d2b35662007975b5dd49ee3144f5809f1 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 23:34:49 -0400 Subject: [PATCH 27/80] Remove enable_event_loop() --- ncc/include/uvm/utils.h | 29 ----------------------------- ncc/src/codegen.rs | 11 +---------- ncc/src/exec_tests.rs | 16 ++++++++-------- ncc/tests/uvm_time.c | 36 ------------------------------------ 4 files changed, 9 insertions(+), 83 deletions(-) delete mode 100644 ncc/tests/uvm_time.c diff --git a/ncc/include/uvm/utils.h b/ncc/include/uvm/utils.h index 770f5de..9d89a6b 100644 --- a/ncc/include/uvm/utils.h +++ b/ncc/include/uvm/utils.h @@ -3,35 +3,6 @@ #include -// Set a hidden global variable to enable the UVM event loop -// This will cause your program to continue running after -// your main() function returns. -void enable_event_loop() -{ - asm () -> void - { - push __EVENT_LOOP_ENABLED__; - push 1; - store_u8; - }; -} - -// Schedule a new update at a fixed rate -// This takes into account the time taken by the current update -void fixed_rate_update(u64 start_time, u64 rate_ms, void* callback) -{ - assert(rate_ms > 0); - - u64 cur_time_ms = asm () -> u64 { syscall time_current_ms; }; - u64 time_taken = cur_time_ms - start_time; - - // Compute when to do the next update - u64 next_update = (time_taken > rate_ms)? (u64)0:(rate_ms - time_taken); - - // Schedule the next update - asm (next_update, callback) -> void { syscall time_delay_cb; }; -} - // Macro to benchmark an expression or statement #ifndef benchmark #define benchmark(expr) \ diff --git a/ncc/src/codegen.rs b/ncc/src/codegen.rs index 946578e..e161414 100644 --- a/ncc/src/codegen.rs +++ b/ncc/src/codegen.rs @@ -123,10 +123,6 @@ impl Unit out.push_str(".u64 0xBADADD5EFEFEFEFE;\n"); out.push_str("\n"); - out.push_str("__EVENT_LOOP_ENABLED__:\n"); - out.push_str(".u8 0;\n"); - out.push_str("\n"); - // Global variable initialization for global in &self.global_vars { // Align the data @@ -169,17 +165,12 @@ impl Unit out.push_str("# call the main function and then exit\n"); out.push_str("call main, 0;\n"); - out.push_str("push __EVENT_LOOP_ENABLED__;\n"); - out.push_str("load_u8;\n"); - out.push_str("jnz __ret_to_event_loop__;\n"); out.push_str("exit;\n"); - out.push_str("__ret_to_event_loop__:\n"); - out.push_str("ret;\n"); out.push_str("\n"); } else { - // If there is no main function, the unit should exit (do nothing) + // If there is no main function, the unit should just exit (do nothing) out.push_str("push 0;\n"); out.push_str("exit;\n"); out.push_str("\n"); diff --git a/ncc/src/exec_tests.rs b/ncc/src/exec_tests.rs index 3e7fde6..113ac48 100644 --- a/ncc/src/exec_tests.rs +++ b/ncc/src/exec_tests.rs @@ -56,6 +56,14 @@ fn exec_tests() let output = command.output().unwrap(); assert!(output.status.success(), "execution failed"); + // Compile all the tests and run them + for file in fs::read_dir("./tests").unwrap() { + let file_path = file.unwrap().path().display().to_string(); + if file_path.ends_with(".c") { + compile_and_run(&file_path, true); + } + } + // We only run a subset of examples // Some examples involve creating a UI window // We parse/validate those without executing them @@ -77,12 +85,4 @@ fn exec_tests() compile_and_run(&file_path, run_example); } } - - // Compile all the tests and run them - for file in fs::read_dir("./tests").unwrap() { - let file_path = file.unwrap().path().display().to_string(); - if file_path.ends_with(".c") { - compile_and_run(&file_path, true); - } - } } diff --git a/ncc/tests/uvm_time.c b/ncc/tests/uvm_time.c deleted file mode 100644 index fe1808c..0000000 --- a/ncc/tests/uvm_time.c +++ /dev/null @@ -1,36 +0,0 @@ -#include -#include -#include -#include - -bool cb1 = false; -bool cb2 = false; - -void callback1() -{ - if (cb2) - exit(0); - - cb1 = true; -} - -void callback2() -{ - if (cb1) - exit(0); - - cb2 = true; -} - -void main() -{ - u64 t0 = time_current_ms(); - u64 t1 = time_current_ms(); - assert(t1 >= t0); - - // This test should only terminate if both callbacks are called, - // otherwise it will hang - time_delay_cb(1, callback1); - time_delay_cb(2, callback2); - enable_event_loop(); -} From f2607c3440ecfcee005c9644e51610b3f7b1c578 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 23:35:41 -0400 Subject: [PATCH 28/80] Re-enable window_create and window_draw_frame syscalls --- vm/src/sys/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index 510a410..3332098 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -119,8 +119,9 @@ pub fn get_syscall(const_idx: u16) -> SysCallFn TIME_CURRENT_MS => SysCallFn::Fn0_1(time_current_ms), - //WINDOW_CREATE => SysCallFn::Fn4_1(window_create), - //WINDOW_DRAW_FRAME => SysCallFn::Fn2_0(window_draw_frame), + WINDOW_CREATE => SysCallFn::Fn4_1(window_create), + WINDOW_DRAW_FRAME => SysCallFn::Fn2_0(window_draw_frame), + // TODO //AUDIO_OPEN_OUTPUT => SysCallFn::Fn4_1(audio_open_output), From b153b9aa2995404ad9a051d77d6d4517f670f276 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 31 Aug 2024 23:53:39 -0400 Subject: [PATCH 29/80] Get memcpy working --- vm/src/main.rs | 1 + vm/src/sys/mod.rs | 32 +++++++++++++------------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/vm/src/main.rs b/vm/src/main.rs index 598402e..861e3f5 100644 --- a/vm/src/main.rs +++ b/vm/src/main.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] #![allow(unused_variables)] #![allow(unused_mut)] +#![allow(unused_parens)] #![allow(unused_imports)] mod vm; diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index 3332098..e3ab426 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -185,54 +185,48 @@ fn memset(thread: &mut Thread, dst_ptr: Value, val: Value, num_bytes: Value) fn memset32(thread: &mut Thread, dst_ptr: Value, word: Value, num_words: Value) { - todo!(); - - /* let dst_ptr = dst_ptr.as_usize(); let word = word.as_u32(); let num_words = num_words.as_usize(); - let mem_slice: &mut [u32] = vm.get_heap_slice(dst_ptr, num_words); + let mem_slice: &mut [u32] = thread.get_heap_slice_mut(dst_ptr, num_words); mem_slice.fill(word); - */ } fn memcpy(thread: &mut Thread, dst_ptr: Value, src_ptr: Value, num_bytes: Value) { - todo!(); - - /* let dst_ptr = dst_ptr.as_usize(); let src_ptr = src_ptr.as_usize(); let num_bytes = num_bytes.as_usize(); - // TODO: panic if slices are overlapping + let overlap = ( + (dst_ptr >= src_ptr && dst_ptr < src_ptr + num_bytes) || + (src_ptr >= dst_ptr && src_ptr < dst_ptr + num_bytes) + ); + + if overlap { + panic!("memcpy to/from overlapping regions"); + } unsafe { - let dst_ptr: *mut u8 = vm.get_heap_ptr(dst_ptr, num_bytes); - let src_ptr: *mut u8 = vm.get_heap_ptr(src_ptr, num_bytes); + let dst_ptr: *mut u8 = thread.get_heap_ptr_mut(dst_ptr, num_bytes); + let src_ptr: *mut u8 = thread.get_heap_ptr_mut(src_ptr, num_bytes); std::ptr::copy_nonoverlapping(src_ptr, dst_ptr, num_bytes); } - */ } fn memcmp(thread: &mut Thread, ptr_a: Value, ptr_b: Value, num_bytes: Value) -> Value { - todo!(); - - /* let num_bytes = num_bytes.as_usize(); unsafe { - let ptr_a: *const libc::c_void = vm.get_heap_ptr(ptr_a.as_usize(), num_bytes); - let ptr_b: *const libc::c_void = vm.get_heap_ptr(ptr_b.as_usize(), num_bytes); + let ptr_a: *const libc::c_void = thread.get_heap_ptr_mut(ptr_a.as_usize(), num_bytes); + let ptr_b: *const libc::c_void = thread.get_heap_ptr_mut(ptr_b.as_usize(), num_bytes); let result = libc::memcmp(ptr_a, ptr_b, num_bytes); - Value::from(result as u64) } - */ } fn print_i64(thread: &mut Thread, v: Value) From e22a91876df237d0f7da5e99170fbcb9406943e2 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sun, 1 Sep 2024 00:13:55 -0400 Subject: [PATCH 30/80] Get vm_grow_heap() working, all compiler tests now working --- ncc/include/stdlib.h | 2 +- vm/src/vm.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ncc/include/stdlib.h b/ncc/include/stdlib.h index bdf72ef..d0509da 100644 --- a/ncc/include/stdlib.h +++ b/ncc/include/stdlib.h @@ -119,7 +119,7 @@ void* malloc(size_t size) // Resize the heap if needed if (__next_alloc__ > __heap_size__) { - __heap_size__ = asm (__next_alloc__) -> u64 { syscall vm_resize_heap; }; + __heap_size__ = asm (__next_alloc__) -> u64 { syscall vm_grow_heap; }; } // Write a magic word at the beginning of the block for safety checks diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 75c9e9e..b441859 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -1680,9 +1680,7 @@ impl VM /// Grow the heap to a new size in bytes pub fn grow_heap(&mut self, num_bytes: usize) -> usize { - todo!(); - - //self.heap.resize(num_bytes) + self.heap.grow(num_bytes) } // Create a new thread From 5e8a95c3f551fe6355dfbfb2da65f35abb6e24b9 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sun, 1 Sep 2024 01:44:28 -0400 Subject: [PATCH 31/80] WIP window event refactoring --- api/syscalls.json | 120 ------------------------------------- doc/syscalls.md | 48 --------------- ncc/examples/ball.c | 27 +++++++-- ncc/include/uvm/syscalls.h | 24 -------- vm/src/sys/constants.rs | 18 ++---- vm/src/sys/window.rs | 11 ++++ 6 files changed, 38 insertions(+), 210 deletions(-) diff --git a/api/syscalls.json b/api/syscalls.json index 837a095..7b9c987 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -370,126 +370,6 @@ "permission": "window_display", "const_idx": 10, "description": "Copy a frame of pixels to be displayed into the window. The frame must have the same width and height as the window. The pixel format is 32 bits per pixel in BGRA byte order, with 8 bits for each component and the B byte at the lowest address." - }, - { - "name": "window_on_mousemove", - "args": [ - [ - "u32", - "window_id" - ], - [ - "void*", - "callback" - ] - ], - "returns": [ - "void", - "" - ], - "permission": "window_display", - "const_idx": 11, - "description": "Register a callback for mouse movement. Mouse x/y coordinates are relative to the top-left corner of the window and may be negative if outside of the window." - }, - { - "name": "window_on_mousedown", - "args": [ - [ - "u32", - "window_id" - ], - [ - "void*", - "callback" - ] - ], - "returns": [ - "void", - "" - ], - "permission": "window_display", - "const_idx": 12, - "description": "Register a callback for mouse button press events." - }, - { - "name": "window_on_mouseup", - "args": [ - [ - "u32", - "window_id" - ], - [ - "void*", - "callback" - ] - ], - "returns": [ - "void", - "" - ], - "permission": "window_display", - "const_idx": 13, - "description": "Register a callback for mouse button release events." - }, - { - "name": "window_on_keydown", - "args": [ - [ - "u32", - "window_id" - ], - [ - "void*", - "callback" - ] - ], - "returns": [ - "void", - "" - ], - "permission": "window_display", - "const_idx": 9, - "description": "Register a callback for key press event." - }, - { - "name": "window_on_keyup", - "args": [ - [ - "u32", - "window_id" - ], - [ - "void*", - "callback" - ] - ], - "returns": [ - "void", - "" - ], - "permission": "window_display", - "const_idx": 15, - "description": "Register a callback for key release event." - }, - { - "name": "window_on_textinput", - "args": [ - [ - "u32", - "window_id" - ], - [ - "void*", - "callback" - ] - ], - "returns": [ - "void", - "" - ], - "permission": "window_display", - "const_idx": 19, - "description": "Register a callback to receive text input. The text is encoded as UTF-8 and the callback is called for each byte input." } ], "constants": [ diff --git a/doc/syscalls.md b/doc/syscalls.md index 04e55da..ec0b5ff 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -206,54 +206,6 @@ void window_draw_frame(u32 window_id, const u8* pixel_data) Copy a frame of pixels to be displayed into the window. The frame must have the same width and height as the window. The pixel format is 32 bits per pixel in BGRA byte order, with 8 bits for each component and the B byte at the lowest address. -## window_on_mousemove - -``` -void window_on_mousemove(u32 window_id, void* callback) -``` - -Register a callback for mouse movement. Mouse x/y coordinates are relative to the top-left corner of the window and may be negative if outside of the window. - -## window_on_mousedown - -``` -void window_on_mousedown(u32 window_id, void* callback) -``` - -Register a callback for mouse button press events. - -## window_on_mouseup - -``` -void window_on_mouseup(u32 window_id, void* callback) -``` - -Register a callback for mouse button release events. - -## window_on_keydown - -``` -void window_on_keydown(u32 window_id, void* callback) -``` - -Register a callback for key press event. - -## window_on_keyup - -``` -void window_on_keyup(u32 window_id, void* callback) -``` - -Register a callback for key release event. - -## window_on_textinput - -``` -void window_on_textinput(u32 window_id, void* callback) -``` - -Register a callback to receive text input. The text is encoded as UTF-8 and the callback is called for each byte input. - ## Constants These are the constants associated with the window subsystem: diff --git a/ncc/examples/ball.c b/ncc/examples/ball.c index 415a174..0ac498e 100644 --- a/ncc/examples/ball.c +++ b/ncc/examples/ball.c @@ -54,7 +54,7 @@ void draw_ball() } } -void anim_callback() +void update() { u64 start_time = time_current_ms(); @@ -91,9 +91,10 @@ void anim_callback() window_draw_frame(0, frame_buffer); // Schedule a fixed rate update for the next frame (60fps) - fixed_rate_update(start_time, 1000 / 60, anim_callback); + //fixed_rate_update(start_time, 1000 / 60, anim_callback); } +/* u16* audio_cb(u16 num_channels, u32 num_samples) { assert(num_channels == 1); @@ -121,6 +122,7 @@ u16* audio_cb(u16 num_channels, u32 num_samples) return AUDIO_BUFFER; } +*/ void keydown(u64 window_id, u16 keycode) { @@ -133,11 +135,24 @@ void keydown(u64 window_id, u16 keycode) void main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "Bouncing Ball Example", 0); - window_on_keydown(0, keydown); - time_delay_cb(0, anim_callback); - audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb); - enable_event_loop(); + for (;;) + { + update(); + + } + + + + //window_on_keydown(0, keydown); + + //time_delay_cb(0, anim_callback); + + //audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb); + + + + } diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index 5690afb..96da6ba 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -85,30 +85,6 @@ // Copy a frame of pixels to be displayed into the window. The frame must have the same width and height as the window. The pixel format is 32 bits per pixel in BGRA byte order, with 8 bits for each component and the B byte at the lowest address. #define window_draw_frame(__window_id, __pixel_data) asm (__window_id, __pixel_data) -> void { syscall window_draw_frame; } -// void window_on_mousemove(u32 window_id, void* callback) -// Register a callback for mouse movement. Mouse x/y coordinates are relative to the top-left corner of the window and may be negative if outside of the window. -#define window_on_mousemove(__window_id, __callback) asm (__window_id, __callback) -> void { syscall window_on_mousemove; } - -// void window_on_mousedown(u32 window_id, void* callback) -// Register a callback for mouse button press events. -#define window_on_mousedown(__window_id, __callback) asm (__window_id, __callback) -> void { syscall window_on_mousedown; } - -// void window_on_mouseup(u32 window_id, void* callback) -// Register a callback for mouse button release events. -#define window_on_mouseup(__window_id, __callback) asm (__window_id, __callback) -> void { syscall window_on_mouseup; } - -// void window_on_keydown(u32 window_id, void* callback) -// Register a callback for key press event. -#define window_on_keydown(__window_id, __callback) asm (__window_id, __callback) -> void { syscall window_on_keydown; } - -// void window_on_keyup(u32 window_id, void* callback) -// Register a callback for key release event. -#define window_on_keyup(__window_id, __callback) asm (__window_id, __callback) -> void { syscall window_on_keyup; } - -// void window_on_textinput(u32 window_id, void* callback) -// Register a callback to receive text input. The text is encoded as UTF-8 and the callback is called for each byte input. -#define window_on_textinput(__window_id, __callback) asm (__window_id, __callback) -> void { syscall window_on_textinput; } - // u32 audio_open_output(u32 sample_rate, u16 num_channels, u16 format, void* callback) // Open an audio output device. #define audio_open_output(__sample_rate, __num_channels, __format, __callback) asm (__sample_rate, __num_channels, __format, __callback) -> u32 { syscall audio_open_output; } diff --git a/vm/src/sys/constants.rs b/vm/src/sys/constants.rs index 3939986..34c4293 100644 --- a/vm/src/sys/constants.rs +++ b/vm/src/sys/constants.rs @@ -15,17 +15,11 @@ pub const PRINT_I64: u16 = 5; pub const PRINT_STR: u16 = 6; pub const PRINT_ENDL: u16 = 7; pub const GETCHAR: u16 = 8; -pub const WINDOW_ON_KEYDOWN: u16 = 9; pub const WINDOW_DRAW_FRAME: u16 = 10; -pub const WINDOW_ON_MOUSEMOVE: u16 = 11; -pub const WINDOW_ON_MOUSEDOWN: u16 = 12; -pub const WINDOW_ON_MOUSEUP: u16 = 13; pub const VM_HEAP_SIZE: u16 = 14; -pub const WINDOW_ON_KEYUP: u16 = 15; pub const MEMSET32: u16 = 16; pub const VM_GROW_HEAP: u16 = 17; pub const AUDIO_OPEN_OUTPUT: u16 = 18; -pub const WINDOW_ON_TEXTINPUT: u16 = 19; pub const PRINT_F32: u16 = 20; pub const NET_LISTEN: u16 = 21; pub const NET_ACCEPT: u16 = 22; @@ -57,17 +51,17 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ Some(SysCallDesc { name: "print_str", const_idx: 6, argc: 1, has_ret: false }), Some(SysCallDesc { name: "print_endl", const_idx: 7, argc: 0, has_ret: false }), Some(SysCallDesc { name: "getchar", const_idx: 8, argc: 0, has_ret: true }), - Some(SysCallDesc { name: "window_on_keydown", const_idx: 9, argc: 2, has_ret: false }), + None, Some(SysCallDesc { name: "window_draw_frame", const_idx: 10, argc: 2, has_ret: false }), - Some(SysCallDesc { name: "window_on_mousemove", const_idx: 11, argc: 2, has_ret: false }), - Some(SysCallDesc { name: "window_on_mousedown", const_idx: 12, argc: 2, has_ret: false }), - Some(SysCallDesc { name: "window_on_mouseup", const_idx: 13, argc: 2, has_ret: false }), + None, + None, + None, Some(SysCallDesc { name: "vm_heap_size", const_idx: 14, argc: 0, has_ret: true }), - Some(SysCallDesc { name: "window_on_keyup", const_idx: 15, argc: 2, has_ret: false }), + None, Some(SysCallDesc { name: "memset32", const_idx: 16, argc: 3, has_ret: false }), Some(SysCallDesc { name: "vm_grow_heap", const_idx: 17, argc: 1, has_ret: true }), Some(SysCallDesc { name: "audio_open_output", const_idx: 18, argc: 4, has_ret: true }), - Some(SysCallDesc { name: "window_on_textinput", const_idx: 19, argc: 2, has_ret: false }), + None, Some(SysCallDesc { name: "print_f32", const_idx: 20, argc: 1, has_ret: false }), Some(SysCallDesc { name: "net_listen", const_idx: 21, argc: 2, has_ret: true }), Some(SysCallDesc { name: "net_accept", const_idx: 22, argc: 4, has_ret: true }), diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs index d46e90a..c63e0bc 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/sys/window.rs @@ -160,6 +160,17 @@ pub fn window_draw_frame(thread: &mut Thread, window_id: Value, src_addr: Value) +pub fn window_poll_event(thread: &mut Thread) -> Value +{ + + + + todo!(); +} + + + + /* /// Process SDL events From e5c79b4b028b76028af3ed03ab24befd65d16cc5 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sun, 1 Sep 2024 12:06:57 -0400 Subject: [PATCH 32/80] Start sketching window_poll_event --- api/syscalls.json | 26 +++++++++++++++++ doc/syscalls.md | 12 ++++++++ ncc/include/uvm/syscalls.h | 6 ++++ vm/src/sys/constants.rs | 5 +++- vm/src/sys/window.rs | 60 ++++++++++++++++++++++++++++---------- 5 files changed, 93 insertions(+), 16 deletions(-) diff --git a/api/syscalls.json b/api/syscalls.json index 7b9c987..ab65b56 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -370,9 +370,35 @@ "permission": "window_display", "const_idx": 10, "description": "Copy a frame of pixels to be displayed into the window. The frame must have the same width and height as the window. The pixel format is 32 bits per pixel in BGRA byte order, with 8 bits for each component and the B byte at the lowest address." + }, + { + "name": "window_poll_event", + "args": [ + [ + "void*", + "p_event" + ] + ], + "returns": [ + "bool", + "event_read" + ], + "permission": "window_display", + "const_idx": 9, + "description": "Try to read an event from the windowing system if available. The event is read into an event struct. Boolean true is returned if an event was read, false if not." } ], "constants": [ + [ + "EVENT_QUIT", + "u16", + 0 + ], + [ + "EVENT_KEYDOWN", + "u16", + 1 + ], [ "KEY_BACKSPACE", "u16", diff --git a/doc/syscalls.md b/doc/syscalls.md index ec0b5ff..a97c089 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -206,9 +206,21 @@ void window_draw_frame(u32 window_id, const u8* pixel_data) Copy a frame of pixels to be displayed into the window. The frame must have the same width and height as the window. The pixel format is 32 bits per pixel in BGRA byte order, with 8 bits for each component and the B byte at the lowest address. +## window_poll_event + +``` +bool window_poll_event(void* p_event) +``` + +**Returns:** `bool event_read` + +Try to read an event from the windowing system if available. The event is read into an event struct. Boolean true is returned if an event was read, false if not. + ## Constants These are the constants associated with the window subsystem: +- `u16 EVENT_QUIT = 0` +- `u16 EVENT_KEYDOWN = 1` - `u16 KEY_BACKSPACE = 8` - `u16 KEY_TAB = 9` - `u16 KEY_RETURN = 10` diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index 96da6ba..cea7170 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -85,6 +85,10 @@ // Copy a frame of pixels to be displayed into the window. The frame must have the same width and height as the window. The pixel format is 32 bits per pixel in BGRA byte order, with 8 bits for each component and the B byte at the lowest address. #define window_draw_frame(__window_id, __pixel_data) asm (__window_id, __pixel_data) -> void { syscall window_draw_frame; } +// bool window_poll_event(void* p_event) +// Try to read an event from the windowing system if available. The event is read into an event struct. Boolean true is returned if an event was read, false if not. +#define window_poll_event(__p_event) asm (__p_event) -> bool { syscall window_poll_event; } + // u32 audio_open_output(u32 sample_rate, u16 num_channels, u16 format, void* callback) // Open an audio output device. #define audio_open_output(__sample_rate, __num_channels, __format, __callback) asm (__sample_rate, __num_channels, __format, __callback) -> u32 { syscall audio_open_output; } @@ -109,6 +113,8 @@ // Close an open socket. #define net_close(__socket_id) asm (__socket_id) -> void { syscall net_close; } +#define EVENT_QUIT 0 +#define EVENT_KEYDOWN 1 #define KEY_BACKSPACE 8 #define KEY_TAB 9 #define KEY_RETURN 10 diff --git a/vm/src/sys/constants.rs b/vm/src/sys/constants.rs index 34c4293..8ecfda8 100644 --- a/vm/src/sys/constants.rs +++ b/vm/src/sys/constants.rs @@ -15,6 +15,7 @@ pub const PRINT_I64: u16 = 5; pub const PRINT_STR: u16 = 6; pub const PRINT_ENDL: u16 = 7; pub const GETCHAR: u16 = 8; +pub const WINDOW_POLL_EVENT: u16 = 9; pub const WINDOW_DRAW_FRAME: u16 = 10; pub const VM_HEAP_SIZE: u16 = 14; pub const MEMSET32: u16 = 16; @@ -51,7 +52,7 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ Some(SysCallDesc { name: "print_str", const_idx: 6, argc: 1, has_ret: false }), Some(SysCallDesc { name: "print_endl", const_idx: 7, argc: 0, has_ret: false }), Some(SysCallDesc { name: "getchar", const_idx: 8, argc: 0, has_ret: true }), - None, + Some(SysCallDesc { name: "window_poll_event", const_idx: 9, argc: 1, has_ret: true }), Some(SysCallDesc { name: "window_draw_frame", const_idx: 10, argc: 2, has_ret: false }), None, None, @@ -76,6 +77,8 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ Some(SysCallDesc { name: "thread_join", const_idx: 31, argc: 1, has_ret: true }), ]; +pub const EVENT_QUIT: u16 = 0; +pub const EVENT_KEYDOWN: u16 = 1; pub const KEY_BACKSPACE: u16 = 8; pub const KEY_TAB: u16 = 9; pub const KEY_RETURN: u16 = 10; diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs index c63e0bc..0524b2f 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/sys/window.rs @@ -10,6 +10,7 @@ use sdl2::render::Texture; use sdl2::render::TextureAccess; use sdl2::pixels::PixelFormatEnum; +use std::mem::size_of; use std::time::Duration; use crate::sys::{get_sdl_context}; @@ -157,15 +158,56 @@ pub fn window_draw_frame(thread: &mut Thread, window_id: Value, src_addr: Value) window.canvas.present(); } +// C event struct +#[repr(C)] +struct CEvent +{ + kind: u16, + window_id: u16, + keycode: u16, + btn_id: u16, +} +/// Takes a pointer ot an event struct as argument +/// Returns true if an event was read +pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value +{ + use crate::sys::constants::*; + let p_event: *mut CEvent = thread.get_heap_ptr_mut(p_event.as_usize(), size_of::()); + let mut c_event = unsafe { &mut *p_event }; -pub fn window_poll_event(thread: &mut Thread) -> Value -{ + let mut event_pump = get_sdl_context().event_pump().unwrap(); + let event = event_pump.poll_event(); + // If no event is available + if event.is_none() { + return Value::from(false); + } + + let event_read = match event.unwrap() { + Event::Quit { .. } => { + c_event.kind = EVENT_QUIT; + true + } + Event::KeyDown { window_id, keycode: Some(keycode), .. } => { + match translate_keycode(keycode) { + Some(keycode) => { + c_event.kind = EVENT_KEYDOWN; + c_event.window_id = 0; + c_event.keycode = keycode; + true + } - todo!(); + None => false + } + } + + _ => false + }; + + Value::from(event_read) } @@ -183,10 +225,6 @@ pub fn process_events(vm: &mut VM) -> ExitReason // TODO: we probably want to process window/input related events in window.rs ? for event in event_pump.poll_iter() { match event { - Event::Quit { .. } => { - return ExitReason::Exit(Value::from(0)); - } - Event::MouseMotion { window_id, x, y, .. } => { if let ExitReason::Exit(val) = window_call_mousemove(vm, window_id, x, y) { return ExitReason::Exit(val); @@ -205,12 +243,6 @@ pub fn process_events(vm: &mut VM) -> ExitReason } } - Event::KeyDown { window_id, keycode: Some(keycode), .. } => { - if let ExitReason::Exit(val) = window_call_keydown(vm, window_id, keycode) { - return ExitReason::Exit(val); - } - } - Event::KeyUp { window_id, keycode: Some(keycode), .. } => { if let ExitReason::Exit(val) = window_call_keyup(vm, window_id, keycode) { return ExitReason::Exit(val); @@ -238,7 +270,6 @@ pub fn process_events(vm: &mut VM) -> ExitReason -/* fn translate_keycode(sdl_keycode: Keycode) -> Option { use crate::sys::constants::*; @@ -306,4 +337,3 @@ fn translate_keycode(sdl_keycode: Keycode) -> Option _ => None } } -*/ From c3119f655821af96403599cf09e7f64195c1ca4f Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sun, 1 Sep 2024 12:13:31 -0400 Subject: [PATCH 33/80] Get ball example working with event polling system --- ncc/examples/ball.c | 38 ++++++++++++++++++++++++-------------- vm/src/sys/mod.rs | 2 +- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/ncc/examples/ball.c b/ncc/examples/ball.c index 0ac498e..851c90a 100644 --- a/ncc/examples/ball.c +++ b/ncc/examples/ball.c @@ -90,8 +90,8 @@ void update() window_draw_frame(0, frame_buffer); - // Schedule a fixed rate update for the next frame (60fps) - //fixed_rate_update(start_time, 1000 / 60, anim_callback); + // Sleep for at least 16ms (max 60fps) + thread_sleep(16); } /* @@ -132,27 +132,37 @@ void keydown(u64 window_id, u16 keycode) } } +typedef struct +{ + u16 kind; + u16 window_id; + u16 keycode; + u16 btn_id; +} Event; + void main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "Bouncing Ball Example", 0); - + Event event; for (;;) { - update(); - - } - - + if (window_poll_event(&event)) + { + if (event.kind == EVENT_QUIT) + { + break; + } - //window_on_keydown(0, keydown); + if (event.kind == EVENT_KEYDOWN && event.keycode == KEY_ESCAPE) + { + break; + } + } - //time_delay_cb(0, anim_callback); + update(); + } //audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb); - - - - } diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index e3ab426..c4fbc4e 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -121,7 +121,7 @@ pub fn get_syscall(const_idx: u16) -> SysCallFn WINDOW_CREATE => SysCallFn::Fn4_1(window_create), WINDOW_DRAW_FRAME => SysCallFn::Fn2_0(window_draw_frame), - // TODO + WINDOW_POLL_EVENT => SysCallFn::Fn1_1(window_poll_event), //AUDIO_OPEN_OUTPUT => SysCallFn::Fn4_1(audio_open_output), From 381f59137321135655611b1de9360afd66100395 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sun, 1 Sep 2024 12:29:51 -0400 Subject: [PATCH 34/80] Handle KeyUp events --- api/syscalls.json | 5 +++++ doc/syscalls.md | 1 + ncc/include/uvm/syscalls.h | 1 + vm/src/sys/constants.rs | 1 + vm/src/sys/window.rs | 13 +++++++++++++ 5 files changed, 21 insertions(+) diff --git a/api/syscalls.json b/api/syscalls.json index ab65b56..7c88fcd 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -399,6 +399,11 @@ "u16", 1 ], + [ + "EVENT_KEYUP", + "u16", + 2 + ], [ "KEY_BACKSPACE", "u16", diff --git a/doc/syscalls.md b/doc/syscalls.md index a97c089..5d833b2 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -221,6 +221,7 @@ These are the constants associated with the window subsystem: - `u16 EVENT_QUIT = 0` - `u16 EVENT_KEYDOWN = 1` +- `u16 EVENT_KEYUP = 2` - `u16 KEY_BACKSPACE = 8` - `u16 KEY_TAB = 9` - `u16 KEY_RETURN = 10` diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index cea7170..d266333 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -115,6 +115,7 @@ #define EVENT_QUIT 0 #define EVENT_KEYDOWN 1 +#define EVENT_KEYUP 2 #define KEY_BACKSPACE 8 #define KEY_TAB 9 #define KEY_RETURN 10 diff --git a/vm/src/sys/constants.rs b/vm/src/sys/constants.rs index 8ecfda8..93abd97 100644 --- a/vm/src/sys/constants.rs +++ b/vm/src/sys/constants.rs @@ -79,6 +79,7 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ pub const EVENT_QUIT: u16 = 0; pub const EVENT_KEYDOWN: u16 = 1; +pub const EVENT_KEYUP: u16 = 2; pub const KEY_BACKSPACE: u16 = 8; pub const KEY_TAB: u16 = 9; pub const KEY_RETURN: u16 = 10; diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs index 0524b2f..dea94ad 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/sys/window.rs @@ -204,6 +204,19 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value } } + Event::KeyUp { window_id, keycode: Some(keycode), .. } => { + match translate_keycode(keycode) { + Some(keycode) => { + c_event.kind = EVENT_KEYUP; + c_event.window_id = 0; + c_event.keycode = keycode; + true + } + + None => false + } + } + _ => false }; From 303b3e0d4651580188dfd0945052a5128715f80a Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sun, 1 Sep 2024 12:44:07 -0400 Subject: [PATCH 35/80] Fix up fire example --- ncc/examples/ball.c | 9 +-------- ncc/examples/fire.c | 37 ++++++++++++++++++++++--------------- ncc/include/uvm/window.h | 12 ++++++++++++ 3 files changed, 35 insertions(+), 23 deletions(-) create mode 100644 ncc/include/uvm/window.h diff --git a/ncc/examples/ball.c b/ncc/examples/ball.c index 851c90a..a4cd017 100644 --- a/ncc/examples/ball.c +++ b/ncc/examples/ball.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -132,14 +133,6 @@ void keydown(u64 window_id, u16 keycode) } } -typedef struct -{ - u16 kind; - u16 window_id; - u16 keycode; - u16 btn_id; -} Event; - void main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "Bouncing Ball Example", 0); diff --git a/ncc/examples/fire.c b/ncc/examples/fire.c index 4a3af15..c60b2a8 100644 --- a/ncc/examples/fire.c +++ b/ncc/examples/fire.c @@ -3,8 +3,8 @@ // https://lodev.org/cgtutor/fire.html #include -#include #include +#include #include #include #include @@ -67,7 +67,7 @@ u32 hsl_to_rgb(float h, float s, float l) } } -void anim_callback() +void update() { u64 frame_start_time = time_current_ms(); @@ -108,16 +108,8 @@ void anim_callback() window_draw_frame(0, frame_buffer); - // Schedule a fixed rate update for the next frame (40fps) - fixed_rate_update(frame_start_time, 1000 / 40, anim_callback); -} - -void keydown(u64 window_id, u16 keycode) -{ - if (keycode == KEY_ESCAPE) - { - exit(0); - } + // Sleep to cap the update rate at 40fps + thread_sleep(1000 / 40); } void main() @@ -135,9 +127,24 @@ void main() } window_create(FRAME_WIDTH, FRAME_HEIGHT, "Demoscene Fire Effect", 0); - window_on_keydown(0, keydown); - time_delay_cb(0, anim_callback); + Event event; - enable_event_loop(); + for (;;) + { + if (window_poll_event(&event)) + { + if (event.kind == EVENT_QUIT) + { + break; + } + + if (event.kind == EVENT_KEYDOWN && event.keycode == KEY_ESCAPE) + { + break; + } + } + + update(); + } } diff --git a/ncc/include/uvm/window.h b/ncc/include/uvm/window.h new file mode 100644 index 0000000..696b7e0 --- /dev/null +++ b/ncc/include/uvm/window.h @@ -0,0 +1,12 @@ +#ifndef __WINDOW_H__ +#define __WINDOW_H__ + +typedef struct +{ + u16 kind; + u16 window_id; + u16 keycode; + u16 btn_id; +} Event; + +#endif From dc4fded9f54de143ee5f4bae2591517c2b2e04fa Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sun, 1 Sep 2024 12:52:27 -0400 Subject: [PATCH 36/80] Mark networking examples as disabled for now --- ncc/examples/{mini_bbs.c => mini_bbs.c.disabled} | 0 ncc/examples/{telnet_server.c => telnet_server.c.disabled} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename ncc/examples/{mini_bbs.c => mini_bbs.c.disabled} (100%) rename ncc/examples/{telnet_server.c => telnet_server.c.disabled} (100%) diff --git a/ncc/examples/mini_bbs.c b/ncc/examples/mini_bbs.c.disabled similarity index 100% rename from ncc/examples/mini_bbs.c rename to ncc/examples/mini_bbs.c.disabled diff --git a/ncc/examples/telnet_server.c b/ncc/examples/telnet_server.c.disabled similarity index 100% rename from ncc/examples/telnet_server.c rename to ncc/examples/telnet_server.c.disabled From a30e548a521a380c1fd29ed88c417988da0070ae Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sun, 1 Sep 2024 19:11:44 -0400 Subject: [PATCH 37/80] Fix snake.c example --- ncc/examples/snake.c | 45 ++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/ncc/examples/snake.c b/ncc/examples/snake.c index ecaec0e..e6bec3d 100644 --- a/ncc/examples/snake.c +++ b/ncc/examples/snake.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -95,7 +96,7 @@ void spawn_apple() } } -void anim_callback() +void update() { u64 start_time = time_current_ms(); @@ -175,36 +176,53 @@ void anim_callback() window_draw_frame(0, frame_buffer); - // Schedule a fixed rate update for the next frame - fixed_rate_update(start_time, 100, anim_callback); + thread_sleep(100); } -void keydown(u64 window_id, u16 keycode) +void read_keys() { + Event event; + + if (!window_poll_event(&event)) + { + return; + } + + if (event.kind == EVENT_QUIT) + { + exit(0); + } + + if (event.kind != EVENT_KEYDOWN) + { + return; + } + + // Current snake x/y direction int sdx = snake_xs[1] - snake_xs[0]; int sdy = snake_ys[1] - snake_ys[0]; - if (keycode == KEY_ESCAPE) + if (event.keycode == KEY_ESCAPE) { exit(0); } - if (keycode == KEY_LEFT && sdx != -1) + if (event.keycode == KEY_LEFT && sdx != -1) { dx = -1; dy = 0; } - else if (keycode == KEY_RIGHT && sdx != 1) + else if (event.keycode == KEY_RIGHT && sdx != 1) { dx = 1; dy = 0; } - else if (keycode == KEY_UP && sdy != -1) + else if (event.keycode == KEY_UP && sdy != -1) { dx = 0; dy = -1; } - else if (keycode == KEY_DOWN && sdy != 1) + else if (event.keycode == KEY_DOWN && sdy != 1) { dx = 0; dy = 1; @@ -214,14 +232,17 @@ void keydown(u64 window_id, u16 keycode) void main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "Snake Game Example", 0); - window_on_keydown(0, keydown); + // Initialize the snake positions for (int i = 0; i < snake_len; ++i) { snake_xs[i] = GRID_WIDTH / 2; snake_ys[i] = (GRID_HEIGHT / 4) - i; } - time_delay_cb(0, anim_callback); - enable_event_loop(); + for (;;) + { + read_keys(); + update(); + } } From 258d90bd621aa987a8a874c9f4c2122da11e75ed Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 2 Sep 2024 13:10:06 -0400 Subject: [PATCH 38/80] Fix simple animation examples --- ncc/examples/3dcube.c | 20 +++-------- ncc/examples/ball.c | 33 +---------------- ncc/examples/counter.c | 8 ++--- ncc/examples/fire.c | 25 +------------ ncc/examples/gameoflife.c | 11 ++---- ncc/examples/plasma.c | 20 ++--------- ncc/examples/snake.c | 76 +++++++++++++++++++-------------------- ncc/examples/thegrid.c | 19 +++------- ncc/include/uvm/utils.h | 7 ++++ ncc/include/uvm/window.h | 50 ++++++++++++++++++++++++++ 10 files changed, 112 insertions(+), 157 deletions(-) diff --git a/ncc/examples/3dcube.c b/ncc/examples/3dcube.c index 621d6b3..e610547 100644 --- a/ncc/examples/3dcube.c +++ b/ncc/examples/3dcube.c @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include @@ -73,7 +73,7 @@ void trans_line3d(mat44 trans, vec3 _v0, vec3 _v1) draw_line3d(v0, v1, COLOR_PURPLE); } -void anim_callback() +void update() { u64 start_time = time_current_ms(); @@ -112,25 +112,11 @@ void anim_callback() u64 end_time = time_current_ms(); printf("render time: %dms\n", end_time - start_time); - - // Schedule a fixed rate update for the next frame (60fps) - fixed_rate_update(start_time, 1000 / 60, anim_callback); -} - -void keydown(u64 window_id, u16 keycode) -{ - if (keycode == KEY_ESCAPE) - { - exit(0); - } } int main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "Rotating 3D Cube Example", 0); - window_on_keydown(0, keydown); - time_delay_cb(0, anim_callback); - enable_event_loop(); // Setup the perspective projection matrix perspective( @@ -144,5 +130,7 @@ int main() // Translation matrix for the cube mat44_translate(cube_pos, trans); + anim_event_loop(60, update); + return 0; } diff --git a/ncc/examples/ball.c b/ncc/examples/ball.c index a4cd017..9a5d69b 100644 --- a/ncc/examples/ball.c +++ b/ncc/examples/ball.c @@ -57,8 +57,6 @@ void draw_ball() void update() { - u64 start_time = time_current_ms(); - // Clear the frame buffer, set all pixels to black memset32(frame_buffer, 0, 800 * 600); @@ -90,9 +88,6 @@ void update() } window_draw_frame(0, frame_buffer); - - // Sleep for at least 16ms (max 60fps) - thread_sleep(16); } /* @@ -125,37 +120,11 @@ u16* audio_cb(u16 num_channels, u32 num_samples) } */ -void keydown(u64 window_id, u16 keycode) -{ - if (keycode == KEY_ESCAPE) - { - exit(0); - } -} - void main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "Bouncing Ball Example", 0); - Event event; - - for (;;) - { - if (window_poll_event(&event)) - { - if (event.kind == EVENT_QUIT) - { - break; - } - - if (event.kind == EVENT_KEYDOWN && event.keycode == KEY_ESCAPE) - { - break; - } - } - - update(); - } + anim_event_loop(60, update); //audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb); } diff --git a/ncc/examples/counter.c b/ncc/examples/counter.c index d68456b..6944cae 100644 --- a/ncc/examples/counter.c +++ b/ncc/examples/counter.c @@ -1,6 +1,6 @@ #include #include -#include +#include #define FRAME_WIDTH 800 #define FRAME_HEIGHT 600 @@ -217,7 +217,7 @@ void draw_number(int xmax, int ymin, int dot_size, int number) } } -void anim_callback() +void update() { // Clear the screen memset(frame_buffer, 0, sizeof(frame_buffer)); @@ -228,7 +228,6 @@ void anim_callback() draw_number(500, 200, 10, (int)seconds); window_draw_frame(0, frame_buffer); - time_delay_cb(25, anim_callback); } void main() @@ -239,6 +238,5 @@ void main() window_create(FRAME_WIDTH, FRAME_HEIGHT, "Counter", 0); - time_delay_cb(0, anim_callback); - enable_event_loop(); + anim_event_loop(40, update); } diff --git a/ncc/examples/fire.c b/ncc/examples/fire.c index c60b2a8..c06611f 100644 --- a/ncc/examples/fire.c +++ b/ncc/examples/fire.c @@ -69,8 +69,6 @@ u32 hsl_to_rgb(float h, float s, float l) void update() { - u64 frame_start_time = time_current_ms(); - // Clear the frame buffer, set all pixels to black memset32(frame_buffer, 0, sizeof(frame_buffer) / 4); @@ -107,9 +105,6 @@ void update() } window_draw_frame(0, frame_buffer); - - // Sleep to cap the update rate at 40fps - thread_sleep(1000 / 40); } void main() @@ -128,23 +123,5 @@ void main() window_create(FRAME_WIDTH, FRAME_HEIGHT, "Demoscene Fire Effect", 0); - Event event; - - for (;;) - { - if (window_poll_event(&event)) - { - if (event.kind == EVENT_QUIT) - { - break; - } - - if (event.kind == EVENT_KEYDOWN && event.keycode == KEY_ESCAPE) - { - break; - } - } - - update(); - } + anim_event_loop(40, update); } diff --git a/ncc/examples/gameoflife.c b/ncc/examples/gameoflife.c index ea9decb..6a40efb 100644 --- a/ncc/examples/gameoflife.c +++ b/ncc/examples/gameoflife.c @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -103,14 +104,9 @@ void update() window_draw_frame(0, frame_buffer); } -void anim_callback() +void bench_update() { - u64 start_time = time_current_ms(); - benchmark(update()); - - // Schedule a fixed rate update for the next frame (20fps) - fixed_rate_update(start_time, 1000 / 20, anim_callback); } void main() @@ -127,6 +123,5 @@ void main() } } - time_delay_cb(0, anim_callback); - enable_event_loop(); + anim_event_loop(20, bench_update); } diff --git a/ncc/examples/plasma.c b/ncc/examples/plasma.c index 04d9e54..21f0920 100644 --- a/ncc/examples/plasma.c +++ b/ncc/examples/plasma.c @@ -3,7 +3,7 @@ // https://lodev.org/cgtutor/plasma.html #include -#include +#include #include #include #include @@ -63,7 +63,7 @@ u32 hsv_to_rgb(float h, float s, float v) return rgb32(vi, p, q); } -void anim_callback() +void update() { u64 frame_start_time = time_current_ms(); int time_ms_i = (int)(frame_start_time - prog_start_time); @@ -82,17 +82,6 @@ void anim_callback() } window_draw_frame(0, frame_buffer); - - // Schedule a fixed rate update for the next frame (40fps) - fixed_rate_update(frame_start_time, 1000 / 40, anim_callback); -} - -void keydown(u64 window_id, u16 keycode) -{ - if (keycode == KEY_ESCAPE) - { - exit(0); - } } void main() @@ -130,9 +119,6 @@ void main() } window_create(FRAME_WIDTH, FRAME_HEIGHT, "Demoscene Plasma Effect", 0); - window_on_keydown(0, keydown); - - time_delay_cb(0, anim_callback); - enable_event_loop(); + anim_event_loop(40, update); } diff --git a/ncc/examples/snake.c b/ncc/examples/snake.c index e6bec3d..e7b03d4 100644 --- a/ncc/examples/snake.c +++ b/ncc/examples/snake.c @@ -98,8 +98,6 @@ void spawn_apple() void update() { - u64 start_time = time_current_ms(); - // Move the snake body forward // We do this from tail to head for (int i = snake_len - 1; i > 0; i = i - 1) @@ -183,49 +181,47 @@ void read_keys() { Event event; - if (!window_poll_event(&event)) - { - return; - } - - if (event.kind == EVENT_QUIT) + while (window_poll_event(&event)) { - exit(0); - } + if (event.kind == EVENT_QUIT) + { + exit(0); + } - if (event.kind != EVENT_KEYDOWN) - { - return; - } + if (event.kind != EVENT_KEYDOWN) + { + continue; + } - // Current snake x/y direction - int sdx = snake_xs[1] - snake_xs[0]; - int sdy = snake_ys[1] - snake_ys[0]; + // Current snake x/y direction + int sdx = snake_xs[1] - snake_xs[0]; + int sdy = snake_ys[1] - snake_ys[0]; - if (event.keycode == KEY_ESCAPE) - { - exit(0); - } + if (event.keycode == KEY_ESCAPE) + { + exit(0); + } - if (event.keycode == KEY_LEFT && sdx != -1) - { - dx = -1; - dy = 0; - } - else if (event.keycode == KEY_RIGHT && sdx != 1) - { - dx = 1; - dy = 0; - } - else if (event.keycode == KEY_UP && sdy != -1) - { - dx = 0; - dy = -1; - } - else if (event.keycode == KEY_DOWN && sdy != 1) - { - dx = 0; - dy = 1; + if (event.keycode == KEY_LEFT && sdx != -1) + { + dx = -1; + dy = 0; + } + else if (event.keycode == KEY_RIGHT && sdx != 1) + { + dx = 1; + dy = 0; + } + else if (event.keycode == KEY_UP && sdy != -1) + { + dx = 0; + dy = -1; + } + else if (event.keycode == KEY_DOWN && sdy != 1) + { + dx = 0; + dy = 1; + } } } diff --git a/ncc/examples/thegrid.c b/ncc/examples/thegrid.c index 9a0fe9f..60388ee 100644 --- a/ncc/examples/thegrid.c +++ b/ncc/examples/thegrid.c @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include @@ -92,7 +92,7 @@ u32 hsl_to_rgb(float h, float s, float l) float line_pos = 1.0f; float anim_time = 0.0f; -void anim_callback() +void update() { u64 start_time = time_current_ms(); @@ -153,25 +153,12 @@ void anim_callback() u64 end_time = time_current_ms(); printf("render time: %dms\n", end_time - start_time); - // Schedule a fixed rate update for the next frame (60fps) - fixed_rate_update(start_time, 1000 / 60, anim_callback); anim_time = anim_time + (1 / 60.0f); } -void keydown(u64 window_id, u16 keycode) -{ - if (keycode == KEY_ESCAPE) - { - exit(0); - } -} - int main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "The Grid", 0); - window_on_keydown(0, keydown); - time_delay_cb(0, anim_callback); - enable_event_loop(); // Set up the perspective projection matrix perspective( @@ -182,5 +169,7 @@ int main() persp ); + anim_event_loop(60, update); + return 0; } diff --git a/ncc/include/uvm/utils.h b/ncc/include/uvm/utils.h index 9d89a6b..32fe563 100644 --- a/ncc/include/uvm/utils.h +++ b/ncc/include/uvm/utils.h @@ -3,6 +3,13 @@ #include + + + + + + + // Macro to benchmark an expression or statement #ifndef benchmark #define benchmark(expr) \ diff --git a/ncc/include/uvm/window.h b/ncc/include/uvm/window.h index 696b7e0..786f26e 100644 --- a/ncc/include/uvm/window.h +++ b/ncc/include/uvm/window.h @@ -1,6 +1,9 @@ #ifndef __WINDOW_H__ #define __WINDOW_H__ +#include +#include + typedef struct { u16 kind; @@ -9,4 +12,51 @@ typedef struct u16 btn_id; } Event; + + +//void (*functionPtr)(); +//typedef void (*UpdateFn)(); + + + +// Simple event loop that tries to update rendering at a fixed rate +// until the user closes the window or presses the escape key +void anim_event_loop(u64 max_fps, void* update_fn) +{ + assert(max_fps > 0); + + u64 frame_time = 1000 / max_fps; + + Event event; + + for (;;) + { + while (window_poll_event(&event)) + { + if (event.kind == EVENT_QUIT) + { + return; + } + + if (event.kind == EVENT_KEYDOWN && event.keycode == KEY_ESCAPE) + { + return; + } + } + + u64 start_time = time_current_ms(); + + // Call the update function + asm (update_fn) -> void { call_fp 1; }; + + u64 end_time = time_current_ms(); + u64 update_time = end_time - start_time; + + if (update_time < frame_time) + { + thread_sleep(frame_time - update_time); + } + } +} + #endif From 30f208250bda3422ff4faa4d374272ee66827bcb Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 2 Sep 2024 14:21:53 -0400 Subject: [PATCH 39/80] Update/fix attackers.c example --- ncc/examples/attackers.c | 107 +++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 50 deletions(-) diff --git a/ncc/examples/attackers.c b/ncc/examples/attackers.c index c208980..103f2f7 100644 --- a/ncc/examples/attackers.c +++ b/ncc/examples/attackers.c @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -182,10 +182,8 @@ void fire_bolt() bolt_y = ship_y - 14; } -void anim_callback() +void update_anim() { - u64 start_time = time_current_ms(); - if (left_down && !right_down) { if (ship_x > 20) @@ -273,13 +271,10 @@ void anim_callback() } window_draw_frame(0, frame_buffer); - - // Schedule a fixed rate update for the next frame (40fps) - fixed_rate_update(start_time, 1000 / 40, anim_callback); } // Enemy movement update -void enemy_callback() +void update_enemies() { if (enemies_live == 0) { @@ -300,58 +295,70 @@ void enemy_callback() enemy_j = enemy_j + 1; } - // Schedule the next update - int delay = 500 - 50 * enemy_j; - if (delay <= 0) delay = 50; - time_delay_cb(delay, enemy_callback); - // Update the step count enemy_steps = enemy_steps + 1; } -void keydown(u64 window_id, u16 keycode) +void main() { - if (keycode == KEY_ESCAPE) - { - exit(0); - } + init(); - if (keycode == KEY_LEFT) - { - left_down = true; - } - else if (keycode == KEY_RIGHT) - { - right_down = true; - } - else if (keycode == KEY_SPACE) - { - fire_bolt(); - } -} + window_create(FRAME_WIDTH, FRAME_HEIGHT, "Galactic Attackers", 0); -void keyup(u64 window_id, u16 keycode) -{ - if (keycode == KEY_LEFT) - { - left_down = false; - } - else if (keycode == KEY_RIGHT) + Event event; + + for (u64 frame_idx = 0;; frame_idx = frame_idx + 1) { - right_down = false; - } -} + while (window_poll_event(&event)) + { + if (event.kind == EVENT_QUIT) + { + exit(0); + } -void main() -{ - init(); + if (event.kind == EVENT_KEYDOWN) + { + if (event.keycode == KEY_ESCAPE) + { + exit(0); + } + else if (event.keycode == KEY_LEFT) + { + left_down = true; + } + else if (event.keycode == KEY_RIGHT) + { + right_down = true; + } + else if (event.keycode == KEY_SPACE) + { + fire_bolt(); + } + } - window_create(FRAME_WIDTH, FRAME_HEIGHT, "Galactic Attackers", 0); - window_on_keydown(0, keydown); - window_on_keyup(0, keyup); + if (event.kind == EVENT_KEYUP) + { + if (event.keycode == KEY_LEFT) + { + left_down = false; + } + else if (event.keycode == KEY_RIGHT) + { + right_down = false; + } + } + } - time_delay_cb(0, anim_callback); - time_delay_cb(1500, enemy_callback); + update_anim(); - enable_event_loop(); + // Enemy update rate + int enemy_rate = 18 - 2 * enemy_j; + if (enemy_rate <= 2) enemy_rate = 2; + + if (frame_idx > 40 && frame_idx % enemy_rate == 0) + update_enemies(); + + // 60fps max + thread_sleep(1000 / 60); + } } From 16c92eb2f1af4bceb9edb6b5af7ea85e94ce70cd Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 2 Sep 2024 14:25:46 -0400 Subject: [PATCH 40/80] Add x/y fields to event struct --- ncc/include/uvm/window.h | 9 ++------- vm/src/sys/window.rs | 2 ++ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/ncc/include/uvm/window.h b/ncc/include/uvm/window.h index 786f26e..45ab4dd 100644 --- a/ncc/include/uvm/window.h +++ b/ncc/include/uvm/window.h @@ -10,15 +10,10 @@ typedef struct u16 window_id; u16 keycode; u16 btn_id; + i32 x; + i32 y; } Event; - - -//void (*functionPtr)(); -//typedef void (*UpdateFn)(); - - - // Simple event loop that tries to update rendering at a fixed rate // until the user closes the window or presses the escape key void anim_event_loop(u64 max_fps, void* update_fn) diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs index dea94ad..04182c1 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/sys/window.rs @@ -166,6 +166,8 @@ struct CEvent window_id: u16, keycode: u16, btn_id: u16, + x: i32, + y: i32, } /// Takes a pointer ot an event struct as argument From b5b4b47d2bf69581ced2a965d2bbb8a5214d160d Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 2 Sep 2024 15:33:14 -0400 Subject: [PATCH 41/80] Refactor audio code, fix sawtooth_notes.c example --- ncc/examples/sawtooth_notes.c | 11 ++------- ncc/examples/sequencer.c | 25 ++++++++++----------- vm/src/sys/audio.rs | 42 +++++++++++++---------------------- vm/src/sys/mod.rs | 4 ++-- vm/src/vm.rs | 14 +++++++++--- 5 files changed, 43 insertions(+), 53 deletions(-) diff --git a/ncc/examples/sawtooth_notes.c b/ncc/examples/sawtooth_notes.c index 5f9c789..a5ccc8b 100644 --- a/ncc/examples/sawtooth_notes.c +++ b/ncc/examples/sawtooth_notes.c @@ -1,5 +1,4 @@ #include -#include #include #include @@ -24,11 +23,6 @@ size_t sample_idx = 0; // Oscillator phase float phase = 0.0f; -void anim_callback() -{ - exit(0); -} - u16* audio_cb(u16 num_channels, u32 num_samples) { assert(num_channels == 1); @@ -67,9 +61,8 @@ u16* audio_cb(u16 num_channels, u32 num_samples) void main() { - time_delay_cb(8_000, anim_callback); - audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb); - enable_event_loop(); + // Keep the program running until audio is done playing + thread_sleep(8000); } diff --git a/ncc/examples/sequencer.c b/ncc/examples/sequencer.c index 996e0a0..a10e260 100644 --- a/ncc/examples/sequencer.c +++ b/ncc/examples/sequencer.c @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -154,23 +154,22 @@ void mousedown(u64 window_id, u8 btn_id, i32 x, i32 y) redraw(); } -void keydown(u64 window_id, u16 keycode) -{ - if (keycode == KEY_ESCAPE) - { - exit(0); - } -} - void main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "Pentatonic Sequencer", 0); - window_on_keydown(0, keydown); - window_on_mousedown(0, mousedown); - enable_event_loop(); + audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb); redraw(); - audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb); + + + + + + //window_on_keydown(0, keydown); + //window_on_mousedown(0, mousedown); + + + } diff --git a/vm/src/sys/audio.rs b/vm/src/sys/audio.rs index 26bb122..b875a3a 100644 --- a/vm/src/sys/audio.rs +++ b/vm/src/sys/audio.rs @@ -1,21 +1,19 @@ use sdl2::audio::{AudioCallback, AudioSpecDesired, AudioDevice}; use std::sync::{Arc, Weak, Mutex}; -use crate::vm::{Value, VM}; +use crate::vm::{Value, VM, Thread}; use crate::sys::{get_sdl_context}; use crate::sys::constants::*; -#[derive(Clone)] struct AudioCB { - // Weak reference to the VM, guarded by a mutex - // We use this to call the VM to generate audio - vm: Weak>, + // Number of audio output channels + num_channels: usize, + + // VM thread in which to execute the audio callback + thread: Thread, // Callback function pointer cb: u64, - - // Number of audio output channels - num_channels: usize, } impl AudioCallback for AudioCB @@ -31,18 +29,11 @@ impl AudioCallback for AudioCB assert!(output_len % self.num_channels == 0); let samples_per_chan = output_len / self.num_channels; - let arc = self.vm.upgrade().unwrap(); - let mut vm = arc.lock().unwrap(); + // Run the audio callback + let ptr = self.thread.call(self.cb, &[Value::from(self.num_channels), Value::from(samples_per_chan)]); - /* - match vm.call(self.cb, &[Value::from(self.num_channels), Value::from(samples_per_chan)]) { - ExitReason::Return(ptr) => { - let mem_slice: &[i16] = vm.get_heap_slice(ptr.as_usize(), output_len); - out.copy_from_slice(&mem_slice); - } - _ => panic!() - } - */ + let mem_slice: &[i16] = self.thread.get_heap_slice_mut(ptr.as_usize(), output_len); + out.copy_from_slice(&mem_slice); } } @@ -52,13 +43,10 @@ impl AudioCallback for AudioCB /// the Send trait, and so can't be referenced from another thread static mut DEVICE: Option> = None; - - -/* // NOTE: this can only be called from the main thread since it uses SDL // However, it creates a new thread to generate audio sample, this thread // could be given a reference to another VM instance -pub fn audio_open_output(vm: &mut VM, sample_rate: Value, num_channels: Value, format: Value, cb: Value) -> Value +pub fn audio_open_output(thread: &mut Thread, sample_rate: Value, num_channels: Value, format: Value, cb: Value) -> Value { let sample_rate = sample_rate.as_u32(); let num_channels = num_channels.as_u16(); @@ -87,12 +75,15 @@ pub fn audio_open_output(vm: &mut VM, sample_rate: Value, num_channels: Value, f samples: Some(1024) // buffer size, 1024 samples }; + // Create a new VM thread in which to run the audio callback + let audio_thread = VM::new_thread(&thread.vm); + let device = audio_subsystem.open_playback(None, &desired_spec, |spec| { // initialize the audio callback AudioCB { - vm: vm.sys_state.mutex.clone(), + num_channels: num_channels.into(), + thread: audio_thread, cb: cb, - num_channels: num_channels.into() } }).unwrap(); @@ -107,4 +98,3 @@ pub fn audio_open_output(vm: &mut VM, sample_rate: Value, num_channels: Value, f // TODO: return the device_id (u32) Value::from(0) } -*/ diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs index c4fbc4e..f3caa97 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/sys/mod.rs @@ -123,7 +123,7 @@ pub fn get_syscall(const_idx: u16) -> SysCallFn WINDOW_DRAW_FRAME => SysCallFn::Fn2_0(window_draw_frame), WINDOW_POLL_EVENT => SysCallFn::Fn1_1(window_poll_event), - //AUDIO_OPEN_OUTPUT => SysCallFn::Fn4_1(audio_open_output), + AUDIO_OPEN_OUTPUT => SysCallFn::Fn4_1(audio_open_output), _ => panic!("unknown syscall \"{}\"", const_idx), } @@ -162,7 +162,7 @@ fn thread_sleep(thread: &mut Thread, msecs: Value) fn thread_spawn(thread: &mut Thread, fun: Value) -> Value { let callee_pc = fun.as_u64(); - let tid = VM::new_thread(&thread.vm, callee_pc, vec![]); + let tid = VM::spawn_thread(&thread.vm, callee_pc, vec![]); Value::from(tid) } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index b441859..355e7bd 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -1683,8 +1683,8 @@ impl VM self.heap.grow(num_bytes) } - // Create a new thread - pub fn new_thread(vm: &Arc>, callee_pc: u64, args: Vec) -> u64 + // Create a new thread object without beginning execution + pub fn new_thread(vm: &Arc>) -> Thread { // Assign a thread id let mut vm_ref = vm.lock().unwrap(); @@ -1699,9 +1699,17 @@ impl VM let vm_mutex = vm.clone(); + Thread::new(tid, vm_mutex, code, heap) + } + + // Spawn a new thread and begin executing the specified function + pub fn spawn_thread(vm: &Arc>, callee_pc: u64, args: Vec) -> u64 + { + let mut thread = VM::new_thread(vm); + let tid = thread.id; + // Spawn a new thread let handle = thread::spawn(move || { - let mut thread = Thread::new(tid, vm_mutex, code, heap); thread.call(callee_pc, args.as_slice()) }); From 5da9a8a6a445b23d504756abb07070f383f8b557 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 2 Sep 2024 15:46:33 -0400 Subject: [PATCH 42/80] Rename event.keycode to event.key --- ncc/examples/attackers.c | 12 ++++++------ ncc/examples/sequencer.c | 22 +++++++++++++++++++--- ncc/examples/snake.c | 10 +++++----- ncc/include/uvm/utils.h | 7 ------- ncc/include/uvm/window.h | 6 +++--- vm/src/sys/window.rs | 8 ++++---- 6 files changed, 37 insertions(+), 28 deletions(-) diff --git a/ncc/examples/attackers.c b/ncc/examples/attackers.c index 103f2f7..911f9a6 100644 --- a/ncc/examples/attackers.c +++ b/ncc/examples/attackers.c @@ -318,19 +318,19 @@ void main() if (event.kind == EVENT_KEYDOWN) { - if (event.keycode == KEY_ESCAPE) + if (event.key == KEY_ESCAPE) { exit(0); } - else if (event.keycode == KEY_LEFT) + else if (event.key == KEY_LEFT) { left_down = true; } - else if (event.keycode == KEY_RIGHT) + else if (event.key == KEY_RIGHT) { right_down = true; } - else if (event.keycode == KEY_SPACE) + else if (event.key == KEY_SPACE) { fire_bolt(); } @@ -338,11 +338,11 @@ void main() if (event.kind == EVENT_KEYUP) { - if (event.keycode == KEY_LEFT) + if (event.key == KEY_LEFT) { left_down = false; } - else if (event.keycode == KEY_RIGHT) + else if (event.key == KEY_RIGHT) { right_down = false; } diff --git a/ncc/examples/sequencer.c b/ncc/examples/sequencer.c index a10e260..325d358 100644 --- a/ncc/examples/sequencer.c +++ b/ncc/examples/sequencer.c @@ -124,7 +124,7 @@ u16* audio_cb(u16 num_channels, u32 num_samples) sample_idx = 0; // Schedule redrawing as soon as we are done generating audio - time_delay_cb(0, redraw); + //time_delay_cb(0, redraw); } } @@ -162,14 +162,30 @@ void main() redraw(); + for (;;) + { + Event event; + + while (window_poll_event(&event)) + { + if (event.kind == EVENT_QUIT) + { + exit(0); + } + if (event.kind == EVENT_KEYDOWN && event.key == KEY_ESCAPE) + { + exit(0); + } - //window_on_keydown(0, keydown); - //window_on_mousedown(0, mousedown); + } + + thread_sleep(25); + } } diff --git a/ncc/examples/snake.c b/ncc/examples/snake.c index e7b03d4..106a117 100644 --- a/ncc/examples/snake.c +++ b/ncc/examples/snake.c @@ -197,27 +197,27 @@ void read_keys() int sdx = snake_xs[1] - snake_xs[0]; int sdy = snake_ys[1] - snake_ys[0]; - if (event.keycode == KEY_ESCAPE) + if (event.key == KEY_ESCAPE) { exit(0); } - if (event.keycode == KEY_LEFT && sdx != -1) + if (event.key == KEY_LEFT && sdx != -1) { dx = -1; dy = 0; } - else if (event.keycode == KEY_RIGHT && sdx != 1) + else if (event.key == KEY_RIGHT && sdx != 1) { dx = 1; dy = 0; } - else if (event.keycode == KEY_UP && sdy != -1) + else if (event.key == KEY_UP && sdy != -1) { dx = 0; dy = -1; } - else if (event.keycode == KEY_DOWN && sdy != 1) + else if (event.key == KEY_DOWN && sdy != 1) { dx = 0; dy = 1; diff --git a/ncc/include/uvm/utils.h b/ncc/include/uvm/utils.h index 32fe563..9d89a6b 100644 --- a/ncc/include/uvm/utils.h +++ b/ncc/include/uvm/utils.h @@ -3,13 +3,6 @@ #include - - - - - - - // Macro to benchmark an expression or statement #ifndef benchmark #define benchmark(expr) \ diff --git a/ncc/include/uvm/window.h b/ncc/include/uvm/window.h index 45ab4dd..ef9cc0f 100644 --- a/ncc/include/uvm/window.h +++ b/ncc/include/uvm/window.h @@ -8,8 +8,8 @@ typedef struct { u16 kind; u16 window_id; - u16 keycode; - u16 btn_id; + u16 key; + u16 btn; i32 x; i32 y; } Event; @@ -33,7 +33,7 @@ void anim_event_loop(u64 max_fps, void* update_fn) return; } - if (event.kind == EVENT_KEYDOWN && event.keycode == KEY_ESCAPE) + if (event.kind == EVENT_KEYDOWN && event.key == KEY_ESCAPE) { return; } diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs index 04182c1..0e14a0d 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/sys/window.rs @@ -164,8 +164,8 @@ struct CEvent { kind: u16, window_id: u16, - keycode: u16, - btn_id: u16, + key: u16, + btn: u16, x: i32, y: i32, } @@ -198,7 +198,7 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value Some(keycode) => { c_event.kind = EVENT_KEYDOWN; c_event.window_id = 0; - c_event.keycode = keycode; + c_event.key = keycode; true } @@ -211,7 +211,7 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value Some(keycode) => { c_event.kind = EVENT_KEYUP; c_event.window_id = 0; - c_event.keycode = keycode; + c_event.key = keycode; true } From 64478c8d72223c309eaa16dcf6416215e6ed8f83 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 2 Sep 2024 16:12:08 -0400 Subject: [PATCH 43/80] Handle mousedown event --- api/syscalls.json | 10 ++++++++ doc/syscalls.md | 2 ++ ncc/examples/sequencer.c | 19 +++++++-------- ncc/include/uvm/syscalls.h | 2 ++ ncc/include/uvm/window.h | 2 +- vm/src/sys/constants.rs | 2 ++ vm/src/sys/window.rs | 47 +++++++++++++++++++++++--------------- 7 files changed, 53 insertions(+), 31 deletions(-) diff --git a/api/syscalls.json b/api/syscalls.json index 7c88fcd..ffa92a0 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -404,6 +404,16 @@ "u16", 2 ], + [ + "EVENT_MOUSEDOWN", + "u16", + 3 + ], + [ + "EVENT_MOUSEUP", + "u16", + 4 + ], [ "KEY_BACKSPACE", "u16", diff --git a/doc/syscalls.md b/doc/syscalls.md index 5d833b2..21476a2 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -222,6 +222,8 @@ These are the constants associated with the window subsystem: - `u16 EVENT_QUIT = 0` - `u16 EVENT_KEYDOWN = 1` - `u16 EVENT_KEYUP = 2` +- `u16 EVENT_MOUSEDOWN = 3` +- `u16 EVENT_MOUSEUP = 4` - `u16 KEY_BACKSPACE = 8` - `u16 KEY_TAB = 9` - `u16 KEY_RETURN = 10` diff --git a/ncc/examples/sequencer.c b/ncc/examples/sequencer.c index 325d358..4328e2e 100644 --- a/ncc/examples/sequencer.c +++ b/ncc/examples/sequencer.c @@ -92,6 +92,7 @@ u16* audio_cb(u16 num_channels, u32 num_samples) for (int j = 0; j < NUM_ROWS; ++j) { + // If there is no note at this position if (!grid[j][step_idx]) continue; @@ -122,16 +123,13 @@ u16* audio_cb(u16 num_channels, u32 num_samples) // Move to the next step step_idx = (step_idx + 1) % NUM_STEPS; sample_idx = 0; - - // Schedule redrawing as soon as we are done generating audio - //time_delay_cb(0, redraw); } } - return audio_buffer; + return audio_buffer; } -void mousedown(u64 window_id, u8 btn_id, i32 x, i32 y) +void mousedown(u8 btn_id, i32 x, i32 y) { if (btn_id != 0) { @@ -178,14 +176,13 @@ void main() exit(0); } - - - - - - + if (event.kind = EVENT_MOUSEDOWN) + { + mousedown(event.button, event.x, event.y); + } } thread_sleep(25); + redraw(); } } diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index d266333..8214b8f 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -116,6 +116,8 @@ #define EVENT_QUIT 0 #define EVENT_KEYDOWN 1 #define EVENT_KEYUP 2 +#define EVENT_MOUSEDOWN 3 +#define EVENT_MOUSEUP 4 #define KEY_BACKSPACE 8 #define KEY_TAB 9 #define KEY_RETURN 10 diff --git a/ncc/include/uvm/window.h b/ncc/include/uvm/window.h index ef9cc0f..826d97b 100644 --- a/ncc/include/uvm/window.h +++ b/ncc/include/uvm/window.h @@ -9,7 +9,7 @@ typedef struct u16 kind; u16 window_id; u16 key; - u16 btn; + u16 button; i32 x; i32 y; } Event; diff --git a/vm/src/sys/constants.rs b/vm/src/sys/constants.rs index 93abd97..d051c0a 100644 --- a/vm/src/sys/constants.rs +++ b/vm/src/sys/constants.rs @@ -80,6 +80,8 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ pub const EVENT_QUIT: u16 = 0; pub const EVENT_KEYDOWN: u16 = 1; pub const EVENT_KEYUP: u16 = 2; +pub const EVENT_MOUSEDOWN: u16 = 3; +pub const EVENT_MOUSEUP: u16 = 4; pub const KEY_BACKSPACE: u16 = 8; pub const KEY_TAB: u16 = 9; pub const KEY_RETURN: u16 = 10; diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs index 0e14a0d..35abd42 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/sys/window.rs @@ -165,7 +165,7 @@ struct CEvent kind: u16, window_id: u16, key: u16, - btn: u16, + button: u16, x: i32, y: i32, } @@ -201,7 +201,6 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value c_event.key = keycode; true } - None => false } } @@ -214,7 +213,20 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value c_event.key = keycode; true } + None => false + } + } + Event::MouseButtonDown { window_id, which, mouse_btn, x, y, .. } => { + match translate_mouse_button(mouse_btn) { + Some(button) => { + c_event.kind = EVENT_MOUSEDOWN; + c_event.window_id = 0; + c_event.button = button; + c_event.x = x; + c_event.y = y; + true + } None => false } } @@ -227,8 +239,6 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value - - /* /// Process SDL events pub fn process_events(vm: &mut VM) -> ExitReason @@ -245,25 +255,12 @@ pub fn process_events(vm: &mut VM) -> ExitReason return ExitReason::Exit(val); } } - - Event::MouseButtonDown { window_id, which, mouse_btn, x, y, .. } => { - if let ExitReason::Exit(val) = window_call_mousedown(vm, window_id, mouse_btn, x, y) { - return ExitReason::Exit(val); - } - } - Event::MouseButtonUp { window_id, which, mouse_btn, x, y, .. } => { if let ExitReason::Exit(val) = window_call_mouseup(vm, window_id, mouse_btn, x, y) { return ExitReason::Exit(val); } } - Event::KeyUp { window_id, keycode: Some(keycode), .. } => { - if let ExitReason::Exit(val) = window_call_keyup(vm, window_id, keycode) { - return ExitReason::Exit(val); - } - } - Event::TextInput { window_id, text, .. } => { // For each UTF-8 byte of input for ch in text.bytes() { @@ -283,8 +280,6 @@ pub fn process_events(vm: &mut VM) -> ExitReason - - fn translate_keycode(sdl_keycode: Keycode) -> Option { use crate::sys::constants::*; @@ -352,3 +347,17 @@ fn translate_keycode(sdl_keycode: Keycode) -> Option _ => None } } + +fn translate_mouse_button(mouse_btn: MouseButton) -> Option +{ + use crate::sys::constants::*; + + match mouse_btn { + MouseButton::Left => Some(0), + MouseButton::Middle => Some(1), + MouseButton::Right => Some(2), + MouseButton::X1 => Some(3), + MouseButton::X2 => Some(4), + MouseButton::Unknown => None + } +} From 92e4451c55ac13faf8f9146db09a8802df72bc21 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 2 Sep 2024 19:48:57 -0400 Subject: [PATCH 44/80] Fix bug in sequencer.c --- ncc/examples/sequencer.c | 18 +++++++++++------- vm/src/sys/window.rs | 4 +++- vm/src/vm.rs | 4 ++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/ncc/examples/sequencer.c b/ncc/examples/sequencer.c index 4328e2e..d866196 100644 --- a/ncc/examples/sequencer.c +++ b/ncc/examples/sequencer.c @@ -30,6 +30,7 @@ u32 step_idx = 0; u32 sample_idx = 0; // Frequencies for musical notes on the pentatonic scale +// Notes at the top (lowest index) have the highest frequency float NOTE_FREQS[6] = { 330.0f, 294.0f, @@ -85,11 +86,12 @@ u16* audio_cb(u16 num_channels, u32 num_samples) u64 steps_per_sec = beats_per_sec * steps_per_beat; u64 samples_per_step = 44100 / steps_per_sec; - // For each sample to generate - for (int i = 0; i < num_samples; ++i) + // For each sample to write in the audio buffer + for (int buf_idx = 0; buf_idx < num_samples; ++buf_idx) { float out = 0.0f; + // For each row of the sequencer for (int j = 0; j < NUM_ROWS; ++j) { // If there is no note at this position @@ -105,15 +107,16 @@ u16* audio_cb(u16 num_channels, u32 num_samples) // Use a square wave for a retro sound float osc_val = (cycle_pos < 0.5f)? 1.0f:-1.0f; - // Convert the output to signed 16-bit i16 samples out = out + osc_val * 0.3f; } + // Decay envelope float env = 1.0f - (float)(i32)sample_idx / 12000.0f; if (env < 0.0f) env = 0.0f; - audio_buffer[i] = (i16)(5000.0f * out * env); + // Convert the output to signed 16-bit i16 samples + audio_buffer[buf_idx] = (i16)(5000.0f * out * env); sample_idx = sample_idx + 1; @@ -131,6 +134,7 @@ u16* audio_cb(u16 num_channels, u32 num_samples) void mousedown(u8 btn_id, i32 x, i32 y) { + // Only handle left clicks if (btn_id != 0) { return; @@ -152,6 +156,8 @@ void mousedown(u8 btn_id, i32 x, i32 y) redraw(); } +Event event; + void main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "Pentatonic Sequencer", 0); @@ -162,8 +168,6 @@ void main() for (;;) { - Event event; - while (window_poll_event(&event)) { if (event.kind == EVENT_QUIT) @@ -176,7 +180,7 @@ void main() exit(0); } - if (event.kind = EVENT_MOUSEDOWN) + if (event.kind == EVENT_MOUSEDOWN) { mousedown(event.button, event.x, event.y); } diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs index 35abd42..679332b 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/sys/window.rs @@ -176,7 +176,9 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value { use crate::sys::constants::*; - let p_event: *mut CEvent = thread.get_heap_ptr_mut(p_event.as_usize(), size_of::()); + let p_event = p_event.as_usize(); + assert!(p_event != 0); + let p_event: *mut CEvent = thread.get_heap_ptr_mut(p_event, size_of::()); let mut c_event = unsafe { &mut *p_event }; let mut event_pump = get_sdl_context().event_pump().unwrap(); diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 355e7bd..9c36399 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -1,5 +1,5 @@ use std::sync::{Arc, Mutex}; -use std::mem::{transmute, size_of}; +use std::mem::{transmute, size_of, align_of}; use std::collections::{HashSet, HashMap}; use std::thread; use std::ffi::CStr; @@ -561,7 +561,7 @@ impl MemView } // Check that the address is aligned - if addr & (size_of::() - 1) != 0 { + if addr & (align_of::() - 1) != 0 { panic!( "attempting to access data of type {} at unaligned address", std::any::type_name::() From 67370d8d0b5692e722f647f672b1412aa404dd70 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 2 Sep 2024 19:52:46 -0400 Subject: [PATCH 45/80] Handle mouseup and mousemove events --- api/syscalls.json | 5 +++ doc/syscalls.md | 1 + ncc/include/uvm/syscalls.h | 1 + vm/src/sys/constants.rs | 1 + vm/src/sys/window.rs | 68 +++++++++++++++++--------------------- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/api/syscalls.json b/api/syscalls.json index ffa92a0..d0ae4bd 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -414,6 +414,11 @@ "u16", 4 ], + [ + "EVENT_MOUSEMOVE", + "u16", + 5 + ], [ "KEY_BACKSPACE", "u16", diff --git a/doc/syscalls.md b/doc/syscalls.md index 21476a2..fdc7009 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -224,6 +224,7 @@ These are the constants associated with the window subsystem: - `u16 EVENT_KEYUP = 2` - `u16 EVENT_MOUSEDOWN = 3` - `u16 EVENT_MOUSEUP = 4` +- `u16 EVENT_MOUSEMOVE = 5` - `u16 KEY_BACKSPACE = 8` - `u16 KEY_TAB = 9` - `u16 KEY_RETURN = 10` diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index 8214b8f..ce25296 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -118,6 +118,7 @@ #define EVENT_KEYUP 2 #define EVENT_MOUSEDOWN 3 #define EVENT_MOUSEUP 4 +#define EVENT_MOUSEMOVE 5 #define KEY_BACKSPACE 8 #define KEY_TAB 9 #define KEY_RETURN 10 diff --git a/vm/src/sys/constants.rs b/vm/src/sys/constants.rs index d051c0a..152d0c9 100644 --- a/vm/src/sys/constants.rs +++ b/vm/src/sys/constants.rs @@ -82,6 +82,7 @@ pub const EVENT_KEYDOWN: u16 = 1; pub const EVENT_KEYUP: u16 = 2; pub const EVENT_MOUSEDOWN: u16 = 3; pub const EVENT_MOUSEUP: u16 = 4; +pub const EVENT_MOUSEMOVE: u16 = 5; pub const KEY_BACKSPACE: u16 = 8; pub const KEY_TAB: u16 = 9; pub const KEY_RETURN: u16 = 10; diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs index 679332b..8b1f91e 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/sys/window.rs @@ -233,54 +233,46 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value } } - _ => false - }; - - Value::from(event_read) -} - + Event::MouseButtonUp { window_id, which, mouse_btn, x, y, .. } => { + match translate_mouse_button(mouse_btn) { + Some(button) => { + c_event.kind = EVENT_MOUSEUP; + c_event.window_id = 0; + c_event.button = button; + c_event.x = x; + c_event.y = y; + true + } + None => false + } + } + Event::MouseMotion { window_id, x, y, .. } => { + c_event.kind = EVENT_MOUSEMOVE; + c_event.window_id = 0; + c_event.x = x; + c_event.y = y; + true + } -/* -/// Process SDL events -pub fn process_events(vm: &mut VM) -> ExitReason -{ - let mut event_pump = get_sdl_context().event_pump().unwrap(); - // Process all pending events - // See: https://docs.rs/sdl2/0.30.0/sdl2/event/enum.Event.html - // TODO: we probably want to process window/input related events in window.rs ? - for event in event_pump.poll_iter() { - match event { - Event::MouseMotion { window_id, x, y, .. } => { - if let ExitReason::Exit(val) = window_call_mousemove(vm, window_id, x, y) { + /* + Event::TextInput { window_id, text, .. } => { + // For each UTF-8 byte of input + for ch in text.bytes() { + if let ExitReason::Exit(val) = window_call_textinput(vm, window_id, ch) { return ExitReason::Exit(val); } } - Event::MouseButtonUp { window_id, which, mouse_btn, x, y, .. } => { - if let ExitReason::Exit(val) = window_call_mouseup(vm, window_id, mouse_btn, x, y) { - return ExitReason::Exit(val); - } - } - - Event::TextInput { window_id, text, .. } => { - // For each UTF-8 byte of input - for ch in text.bytes() { - if let ExitReason::Exit(val) = window_call_textinput(vm, window_id, ch) { - return ExitReason::Exit(val); - } - } - } - - _ => {} } - } + */ - return ExitReason::default(); -} -*/ + _ => false + }; + Value::from(event_read) +} fn translate_keycode(sdl_keycode: Keycode) -> Option { From e5adcf61deb2532f4d6975e7905547605941a9f9 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 2 Sep 2024 20:19:39 -0400 Subject: [PATCH 46/80] Fix examples/raycaster.c --- ncc/examples/raycaster.c | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/ncc/examples/raycaster.c b/ncc/examples/raycaster.c index c250927..2564af9 100644 --- a/ncc/examples/raycaster.c +++ b/ncc/examples/raycaster.c @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -212,7 +212,7 @@ void paint_column(int col_idx, float dx, float dy, float frame_dst, float ray_ds } } -void anim_callback() +void draw_frame() { u64 frame_start_time = time_current_ms(); @@ -340,12 +340,9 @@ void anim_callback() printf("render time %d ms\n", frame_end_time - frame_start_time); window_draw_frame(0, frame_buffer); - - // Schedule a fixed rate update for the next frame (30fps) - fixed_rate_update(frame_start_time, 1000 / 30, anim_callback); } -void keydown(u64 window_id, u16 keycode) +void keydown(u16 keycode) { if (keycode == KEY_ESCAPE) { @@ -385,12 +382,27 @@ void keydown(u64 window_id, u16 keycode) } } +Event event; + void main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "Ray-Casting Example", 0); - window_on_keydown(0, keydown); - time_delay_cb(0, anim_callback); + for (;;) + { + while (window_poll_event(&event)) + { + if (event.kind == EVENT_QUIT) + { + exit(0); + } + + if (event.kind == EVENT_KEYDOWN) + { + keydown(event.key); + } + } - enable_event_loop(); + draw_frame(); + } } From dc0e313cd196eb4f847e84a1e54e1587d58b090c Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 2 Sep 2024 20:51:11 -0400 Subject: [PATCH 47/80] Fix monogram.c and other examples --- ncc/examples/attackers.c | 4 ++-- ncc/examples/monogram.c | 9 +++------ ncc/examples/monogram.py | 9 +++------ ncc/examples/snake.c | 4 ++-- ncc/include/uvm/window.h | 11 ++++++----- 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/ncc/examples/attackers.c b/ncc/examples/attackers.c index 911f9a6..1201641 100644 --- a/ncc/examples/attackers.c +++ b/ncc/examples/attackers.c @@ -299,14 +299,14 @@ void update_enemies() enemy_steps = enemy_steps + 1; } +Event event; + void main() { init(); window_create(FRAME_WIDTH, FRAME_HEIGHT, "Galactic Attackers", 0); - Event event; - for (u64 frame_idx = 0;; frame_idx = frame_idx + 1) { while (window_poll_event(&event)) diff --git a/ncc/examples/monogram.c b/ncc/examples/monogram.c index 5bb22dc..3ede7cb 100644 --- a/ncc/examples/monogram.c +++ b/ncc/examples/monogram.c @@ -2,7 +2,7 @@ // Monogram 12x7 pixel font #include -#include +#include // > # MONOGRAM FONT @@ -585,7 +585,7 @@ u32 FRAME_HEIGHT = 400; u32 frame_buffer[161600]; -void anim_callback() +void draw() { u8 scale = 2; @@ -620,15 +620,12 @@ void anim_callback() } window_draw_frame(0, frame_buffer); - time_delay_cb(10, anim_callback); } void main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "Monogram Font Example", 0); - time_delay_cb(0, anim_callback); - - enable_event_loop(); + anim_event_loop(10, draw); } diff --git a/ncc/examples/monogram.py b/ncc/examples/monogram.py index d1be8a6..fb664d6 100644 --- a/ncc/examples/monogram.py +++ b/ncc/examples/monogram.py @@ -3,7 +3,7 @@ // Monogram 12x7 pixel font #include -#include +#include ''' @@ -508,7 +508,7 @@ def make_bytes(ch): u32 frame_buffer[{202 * 200 * SCALE * SCALE}]; -void anim_callback() +void draw() {{ u8 scale = {SCALE}; @@ -543,15 +543,12 @@ def make_bytes(ch): }} window_draw_frame(0, frame_buffer); - time_delay_cb(10, anim_callback); }} void main() {{ window_create(FRAME_WIDTH, FRAME_HEIGHT, "Monogram Font Example", 0); - time_delay_cb(0, anim_callback); - - enable_event_loop(); + anim_event_loop(10, draw); }} ''') diff --git a/ncc/examples/snake.c b/ncc/examples/snake.c index 106a117..dcdbc78 100644 --- a/ncc/examples/snake.c +++ b/ncc/examples/snake.c @@ -177,10 +177,10 @@ void update() thread_sleep(100); } +Event event; + void read_keys() { - Event event; - while (window_poll_event(&event)) { if (event.kind == EVENT_QUIT) diff --git a/ncc/include/uvm/window.h b/ncc/include/uvm/window.h index 826d97b..0dc907a 100644 --- a/ncc/include/uvm/window.h +++ b/ncc/include/uvm/window.h @@ -14,6 +14,9 @@ typedef struct i32 y; } Event; +// Stack allocation of structs not yet supported +Event __event__; + // Simple event loop that tries to update rendering at a fixed rate // until the user closes the window or presses the escape key void anim_event_loop(u64 max_fps, void* update_fn) @@ -22,18 +25,16 @@ void anim_event_loop(u64 max_fps, void* update_fn) u64 frame_time = 1000 / max_fps; - Event event; - for (;;) { - while (window_poll_event(&event)) + while (window_poll_event(&__event__)) { - if (event.kind == EVENT_QUIT) + if (__event__.kind == EVENT_QUIT) { return; } - if (event.kind == EVENT_KEYDOWN && event.key == KEY_ESCAPE) + if (__event__.kind == EVENT_KEYDOWN && __event__.key == KEY_ESCAPE) { return; } From 462632d1d62a965f20b60311950152d75772db4a Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 2 Sep 2024 21:09:23 -0400 Subject: [PATCH 48/80] Refactor src dir into flat hierarchy --- api/src/main.rs | 2 +- vm/src/asm.rs | 2 +- vm/src/{sys => }/audio.rs | 4 ++-- vm/src/{sys => }/constants.rs | 0 vm/src/{sys/mod.rs => host.rs} | 16 +++++----------- vm/src/main.rs | 7 ++++++- vm/src/{sys => }/net.rs | 0 vm/src/{sys => }/time.rs | 0 vm/src/vm.rs | 2 +- vm/src/{sys => }/window.rs | 10 ++++------ 10 files changed, 20 insertions(+), 23 deletions(-) rename vm/src/{sys => }/audio.rs (97%) rename vm/src/{sys => }/constants.rs (100%) rename vm/src/{sys/mod.rs => host.rs} (98%) rename vm/src/{sys => }/net.rs (100%) rename vm/src/{sys => }/time.rs (100%) rename vm/src/{sys => }/window.rs (98%) diff --git a/api/src/main.rs b/api/src/main.rs index e40c3f9..4a7225d 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -179,7 +179,7 @@ fn main() let mut file = File::create("syscalls.json").unwrap(); file.write_all(json_output.as_bytes()).unwrap(); - gen_rust_bindings("../vm/src/sys/constants.rs", &subsystems, &idx_to_name); + gen_rust_bindings("../vm/src/constants.rs", &subsystems, &idx_to_name); gen_c_bindings("../ncc/include/uvm/syscalls.h", &subsystems); gen_markdown("../doc/syscalls.md", &subsystems); } diff --git a/vm/src/asm.rs b/vm/src/asm.rs index 3224daa..226b933 100644 --- a/vm/src/asm.rs +++ b/vm/src/asm.rs @@ -506,7 +506,7 @@ impl Assembler pub fn new() -> Self { /// Populate the available syscalls - use crate::sys::constants::SYSCALL_DESCS; + use crate::constants::SYSCALL_DESCS; let mut syscall_map = HashMap::new(); for syscall in SYSCALL_DESCS { if let Some(syscall) = syscall { diff --git a/vm/src/sys/audio.rs b/vm/src/audio.rs similarity index 97% rename from vm/src/sys/audio.rs rename to vm/src/audio.rs index b875a3a..1d97504 100644 --- a/vm/src/sys/audio.rs +++ b/vm/src/audio.rs @@ -1,8 +1,8 @@ use sdl2::audio::{AudioCallback, AudioSpecDesired, AudioDevice}; use std::sync::{Arc, Weak, Mutex}; use crate::vm::{Value, VM, Thread}; -use crate::sys::{get_sdl_context}; -use crate::sys::constants::*; +use crate::host::{get_sdl_context}; +use crate::constants::*; struct AudioCB { diff --git a/vm/src/sys/constants.rs b/vm/src/constants.rs similarity index 100% rename from vm/src/sys/constants.rs rename to vm/src/constants.rs diff --git a/vm/src/sys/mod.rs b/vm/src/host.rs similarity index 98% rename from vm/src/sys/mod.rs rename to vm/src/host.rs index f3caa97..ef885f0 100644 --- a/vm/src/sys/mod.rs +++ b/vm/src/host.rs @@ -1,9 +1,3 @@ -pub mod window; -pub mod audio; -pub mod net; -pub mod time; -pub mod constants; - extern crate sdl2; use std::collections::HashMap; use std::io::Write; @@ -11,11 +5,11 @@ use std::io::Read; use std::io::{stdout, stdin}; use std::sync::{Arc, Weak, Mutex}; use crate::vm::{Value, VM, Thread}; -use window::*; -use audio::*; -use net::*; -use time::*; -use constants::*; +use crate::window::*; +use crate::audio::*; +use crate::net::*; +use crate::time::*; +use crate::constants::*; /// System call function signature /// Note: the in/out arg count should be fixed so diff --git a/vm/src/main.rs b/vm/src/main.rs index 861e3f5..dfd8919 100644 --- a/vm/src/main.rs +++ b/vm/src/main.rs @@ -4,8 +4,13 @@ #![allow(unused_parens)] #![allow(unused_imports)] +mod window; +mod audio; +mod net; +mod time; +mod constants; +mod host; mod vm; -mod sys; mod asm; mod program; mod utils; diff --git a/vm/src/sys/net.rs b/vm/src/net.rs similarity index 100% rename from vm/src/sys/net.rs rename to vm/src/net.rs diff --git a/vm/src/sys/time.rs b/vm/src/time.rs similarity index 100% rename from vm/src/sys/time.rs rename to vm/src/time.rs diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 9c36399..1510e3b 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -3,7 +3,7 @@ use std::mem::{transmute, size_of, align_of}; use std::collections::{HashSet, HashMap}; use std::thread; use std::ffi::CStr; -use crate::sys::*; +use crate::host::*; use crate::program::Program; /// Instruction opcodes diff --git a/vm/src/sys/window.rs b/vm/src/window.rs similarity index 98% rename from vm/src/sys/window.rs rename to vm/src/window.rs index 8b1f91e..0d63cff 100644 --- a/vm/src/sys/window.rs +++ b/vm/src/window.rs @@ -9,11 +9,9 @@ use sdl2::surface::Surface; use sdl2::render::Texture; use sdl2::render::TextureAccess; use sdl2::pixels::PixelFormatEnum; - use std::mem::size_of; use std::time::Duration; - -use crate::sys::{get_sdl_context}; +use crate::host::{get_sdl_context}; use crate::vm::{VM, Thread, Value}; /// SDL video subsystem @@ -174,7 +172,7 @@ struct CEvent /// Returns true if an event was read pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value { - use crate::sys::constants::*; + use crate::constants::*; let p_event = p_event.as_usize(); assert!(p_event != 0); @@ -276,7 +274,7 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value fn translate_keycode(sdl_keycode: Keycode) -> Option { - use crate::sys::constants::*; + use crate::constants::*; // https://docs.rs/sdl2/0.30.0/sdl2/keyboard/enum.Keycode.html match sdl_keycode { @@ -344,7 +342,7 @@ fn translate_keycode(sdl_keycode: Keycode) -> Option fn translate_mouse_button(mouse_btn: MouseButton) -> Option { - use crate::sys::constants::*; + use crate::constants::*; match mouse_btn { MouseButton::Left => Some(0), From 093c9f65d6ddb7559945a5dc83783c596addd85c Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 2 Sep 2024 21:23:27 -0400 Subject: [PATCH 49/80] Update README.md --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 715b4fa..97001af 100644 --- a/README.md +++ b/README.md @@ -40,14 +40,11 @@ Current features: - Event-driven event execution model compatible with async operations - Easy to use frame buffer to draw RGB graphics with no boilerplate - Easy to use audio output API with no boilerplate +- Thread-based parallelism Planned future features: -- Async file and network I/O with callbacks - - Synchronous I/O possible as well -- Fast JIT compiler based on dynamic binary translation and basic block versioning - - Expected performance ~80% of native speed (maybe more?) - - Near-instant warmup -- Permission system to safely sandbox apps without granting access to entire computer +- Simple networking API +- Capability system to safely sandbox apps without granting access to entire computer - Ability to compile without SDL and without graphics/audio for headless server-side use - Ability to encode metadata such as author name and app icon into app image files - Ability to suspend running programs and save them to a new app image file From 885ccf5fddb3050ddd4ac731ccbeebbdad5599b2 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Mon, 2 Sep 2024 21:45:10 -0400 Subject: [PATCH 50/80] Remove obsolete time_delay_cb syscall --- api/syscalls.json | 20 -------------------- doc/syscalls.md | 8 -------- ncc/include/uvm/syscalls.h | 4 ---- vm/src/constants.rs | 3 +-- 4 files changed, 1 insertion(+), 34 deletions(-) diff --git a/api/syscalls.json b/api/syscalls.json index d0ae4bd..c31a731 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -295,26 +295,6 @@ "permission": "time_get_time", "const_idx": 0, "description": "Get the UNIX time stamp in milliseconds." - }, - { - "name": "time_delay_cb", - "args": [ - [ - "u64", - "delay_ms" - ], - [ - "void*", - "callback" - ] - ], - "returns": [ - "void", - "" - ], - "permission": "default_allowed", - "const_idx": 2, - "description": "Schedule a callback to be called once after a given delay." } ], "constants": [] diff --git a/doc/syscalls.md b/doc/syscalls.md index fdc7009..c6ca6ed 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -176,14 +176,6 @@ u64 time_current_ms() Get the UNIX time stamp in milliseconds. -## time_delay_cb - -``` -void time_delay_cb(u64 delay_ms, void* callback) -``` - -Schedule a callback to be called once after a given delay. - # window Functionality related to creating windows, drawing graphics, as well as mouse and keyboard input. diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index ce25296..5e17ef6 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -73,10 +73,6 @@ // Get the UNIX time stamp in milliseconds. #define time_current_ms() asm () -> u64 { syscall time_current_ms; } -// void time_delay_cb(u64 delay_ms, void* callback) -// Schedule a callback to be called once after a given delay. -#define time_delay_cb(__delay_ms, __callback) asm (__delay_ms, __callback) -> void { syscall time_delay_cb; } - // u32 window_create(u32 width, u32 height, const char* title, u64 flags) // Create a new window with a frame buffer to draw into. The window is initially hidden when created, and will appear as soon as the first frame of image data is drawn. #define window_create(__width, __height, __title, __flags) asm (__width, __height, __title, __flags) -> u32 { syscall window_create; } diff --git a/vm/src/constants.rs b/vm/src/constants.rs index 152d0c9..7438f78 100644 --- a/vm/src/constants.rs +++ b/vm/src/constants.rs @@ -8,7 +8,6 @@ pub const SYSCALL_TBL_LEN: usize = 32; pub const TIME_CURRENT_MS: u16 = 0; pub const WINDOW_CREATE: u16 = 1; -pub const TIME_DELAY_CB: u16 = 2; pub const MEMCPY: u16 = 3; pub const MEMSET: u16 = 4; pub const PRINT_I64: u16 = 5; @@ -45,7 +44,7 @@ pub struct SysCallDesc pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ Some(SysCallDesc { name: "time_current_ms", const_idx: 0, argc: 0, has_ret: true }), Some(SysCallDesc { name: "window_create", const_idx: 1, argc: 4, has_ret: true }), - Some(SysCallDesc { name: "time_delay_cb", const_idx: 2, argc: 2, has_ret: false }), + None, Some(SysCallDesc { name: "memcpy", const_idx: 3, argc: 3, has_ret: false }), Some(SysCallDesc { name: "memset", const_idx: 4, argc: 3, has_ret: false }), Some(SysCallDesc { name: "print_i64", const_idx: 5, argc: 1, has_ret: false }), From ac931060f8368eb0c177285251d23a885c9782b7 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 12:08:02 -0400 Subject: [PATCH 51/80] Implement window_wait_event. Fix paint.c example --- api/syscalls.json | 16 +++++++++++++++ doc/syscalls.md | 8 ++++++++ ncc/examples/paint.c | 40 +++++++++++++++++++++++++++--------- ncc/include/uvm/syscalls.h | 4 ++++ vm/examples/gradient.asm | 8 ++++++++ vm/src/constants.rs | 3 ++- vm/src/host.rs | 1 + vm/src/window.rs | 42 ++++++++++++++++++++++++++++++-------- 8 files changed, 102 insertions(+), 20 deletions(-) diff --git a/api/syscalls.json b/api/syscalls.json index c31a731..779cf58 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -366,6 +366,22 @@ "permission": "window_display", "const_idx": 9, "description": "Try to read an event from the windowing system if available. The event is read into an event struct. Boolean true is returned if an event was read, false if not." + }, + { + "name": "window_wait_event", + "args": [ + [ + "void*", + "p_event" + ] + ], + "returns": [ + "void", + "" + ], + "permission": "window_display", + "const_idx": 2, + "description": "Block until an window event is available." } ], "constants": [ diff --git a/doc/syscalls.md b/doc/syscalls.md index c6ca6ed..0afb715 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -208,6 +208,14 @@ bool window_poll_event(void* p_event) Try to read an event from the windowing system if available. The event is read into an event struct. Boolean true is returned if an event was read, false if not. +## window_wait_event + +``` +void window_wait_event(void* p_event) +``` + +Block until an window event is available. + ## Constants These are the constants associated with the window subsystem: diff --git a/ncc/examples/paint.c b/ncc/examples/paint.c index 6f809e5..fc73979 100644 --- a/ncc/examples/paint.c +++ b/ncc/examples/paint.c @@ -1,5 +1,5 @@ #include -#include +#include #include #define FRAME_WIDTH 800 @@ -120,7 +120,7 @@ void draw_palette() } // Mouve movement callback -void mousemove(u64 window_id, int new_x, int new_y) +void mousemove(int new_x, int new_y) { if (drawing) { @@ -148,7 +148,7 @@ void mousemove(u64 window_id, int new_x, int new_y) window_draw_frame(0, frame_buffer); } -void mousedown(u64 window_id, u8 btn_id) +void mousedown(u16 btn_id) { if (btn_id == 0) { @@ -167,7 +167,7 @@ void mousedown(u64 window_id, u8 btn_id) window_draw_frame(0, frame_buffer); } -void mouseup(u64 window_id, u8 btn_id) +void mouseup(u16 btn_id) { if (btn_id == 0) { @@ -175,6 +175,8 @@ void mouseup(u64 window_id, u8 btn_id) } } +Event event; + void main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "UVM Paint Program Example", 0); @@ -193,12 +195,30 @@ void main() draw_palette(); - // Register mouse event callbacks - window_on_mousemove(0, mousemove); - window_on_mousedown(0, mousedown); - window_on_mouseup(0, mouseup); - window_draw_frame(0, frame_buffer); - enable_event_loop(); + for (;;) + { + window_wait_event(&event); + + if (event.kind == EVENT_QUIT) + { + exit(0); + } + + if (event.kind == EVENT_MOUSEDOWN) + { + mousedown(event.button); + } + + if (event.kind == EVENT_MOUSEUP) + { + mouseup(event.button); + } + + if (event.kind == EVENT_MOUSEMOVE) + { + mousemove(event.x, event.y); + } + } } diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index 5e17ef6..aa624c3 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -85,6 +85,10 @@ // Try to read an event from the windowing system if available. The event is read into an event struct. Boolean true is returned if an event was read, false if not. #define window_poll_event(__p_event) asm (__p_event) -> bool { syscall window_poll_event; } +// void window_wait_event(void* p_event) +// Block until an window event is available. +#define window_wait_event(__p_event) asm (__p_event) -> void { syscall window_wait_event; } + // u32 audio_open_output(u32 sample_rate, u16 num_channels, u16 format, void* callback) // Open an audio output device. #define audio_open_output(__sample_rate, __num_channels, __format, __callback) asm (__sample_rate, __num_channels, __format, __callback) -> u32 { syscall audio_open_output; } diff --git a/vm/examples/gradient.asm b/vm/examples/gradient.asm index 8b4459a..d0ce939 100644 --- a/vm/examples/gradient.asm +++ b/vm/examples/gradient.asm @@ -118,6 +118,14 @@ push 0; push PIXEL_BUFFER; syscall window_draw_frame; + + +#syscall window_wait_event; + + + + + # Return to the event loop push 0; ret; diff --git a/vm/src/constants.rs b/vm/src/constants.rs index 7438f78..2fe3058 100644 --- a/vm/src/constants.rs +++ b/vm/src/constants.rs @@ -8,6 +8,7 @@ pub const SYSCALL_TBL_LEN: usize = 32; pub const TIME_CURRENT_MS: u16 = 0; pub const WINDOW_CREATE: u16 = 1; +pub const WINDOW_WAIT_EVENT: u16 = 2; pub const MEMCPY: u16 = 3; pub const MEMSET: u16 = 4; pub const PRINT_I64: u16 = 5; @@ -44,7 +45,7 @@ pub struct SysCallDesc pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ Some(SysCallDesc { name: "time_current_ms", const_idx: 0, argc: 0, has_ret: true }), Some(SysCallDesc { name: "window_create", const_idx: 1, argc: 4, has_ret: true }), - None, + Some(SysCallDesc { name: "window_wait_event", const_idx: 2, argc: 1, has_ret: false }), Some(SysCallDesc { name: "memcpy", const_idx: 3, argc: 3, has_ret: false }), Some(SysCallDesc { name: "memset", const_idx: 4, argc: 3, has_ret: false }), Some(SysCallDesc { name: "print_i64", const_idx: 5, argc: 1, has_ret: false }), diff --git a/vm/src/host.rs b/vm/src/host.rs index ef885f0..0bb2ff6 100644 --- a/vm/src/host.rs +++ b/vm/src/host.rs @@ -116,6 +116,7 @@ pub fn get_syscall(const_idx: u16) -> SysCallFn WINDOW_CREATE => SysCallFn::Fn4_1(window_create), WINDOW_DRAW_FRAME => SysCallFn::Fn2_0(window_draw_frame), WINDOW_POLL_EVENT => SysCallFn::Fn1_1(window_poll_event), + WINDOW_WAIT_EVENT => SysCallFn::Fn1_0(window_wait_event), AUDIO_OPEN_OUTPUT => SysCallFn::Fn4_1(audio_open_output), diff --git a/vm/src/window.rs b/vm/src/window.rs index 0d63cff..9514df5 100644 --- a/vm/src/window.rs +++ b/vm/src/window.rs @@ -168,12 +168,10 @@ struct CEvent y: i32, } -/// Takes a pointer ot an event struct as argument +/// Takes a pointer ot an event struct to write into /// Returns true if an event was read pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value { - use crate::constants::*; - let p_event = p_event.as_usize(); assert!(p_event != 0); let p_event: *mut CEvent = thread.get_heap_ptr_mut(p_event, size_of::()); @@ -187,7 +185,37 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value return Value::from(false); } - let event_read = match event.unwrap() { + let event_read = translate_event(event.unwrap(), c_event); + Value::from(event_read) +} + +/// Takes a pointer ot an event struct as argument +/// Blocks until an event is read +pub fn window_wait_event(thread: &mut Thread, p_event: Value) +{ + let p_event = p_event.as_usize(); + assert!(p_event != 0); + let p_event: *mut CEvent = thread.get_heap_ptr_mut(p_event, size_of::()); + let mut c_event = unsafe { &mut *p_event }; + + let mut event_pump = get_sdl_context().event_pump().unwrap(); + + loop + { + let event = event_pump.wait_event(); + + if translate_event(event, c_event) { + break; + } + } +} + +/// Translate an SDL event and write the result to an event struct in memory +fn translate_event(sdl_event: Event, c_event: &mut CEvent) -> bool +{ + use crate::constants::*; + + match sdl_event { Event::Quit { .. } => { c_event.kind = EVENT_QUIT; true @@ -253,7 +281,6 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value true } - /* Event::TextInput { window_id, text, .. } => { // For each UTF-8 byte of input @@ -265,11 +292,8 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value } */ - _ => false - }; - - Value::from(event_read) + } } fn translate_keycode(sdl_keycode: Keycode) -> Option From f721feee73b2f3648463d0b29baecb0fec925aca Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 12:10:15 -0400 Subject: [PATCH 52/80] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 97001af..785813b 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,12 @@ Current features: - Untyped design for simplicity - Little-endian byte ordering (like x86, ARM & RISC-V) - 32-bit and 64-bit integer ops, 32-bit floating-point support -- [Separate flat, linear address spaces for code and data](https://en.wikipedia.org/wiki/Harvard_architecture) +- Separate flat, linear address spaces for code and data (Harvard architecture) +- Thread-based parallelism - Built-in, easy to use [assembler](vm/src/asm.rs) with a [simple syntax](vm/examples) - Event-driven event execution model compatible with async operations - Easy to use frame buffer to draw RGB graphics with no boilerplate - Easy to use audio output API with no boilerplate -- Thread-based parallelism Planned future features: - Simple networking API From d3c3293ea2c358b7c7d57a10b933e845d9b2947a Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 12:12:13 -0400 Subject: [PATCH 53/80] Remove broken circle.asm example --- vm/examples/circle.asm | 218 ----------------------------------------- vm/src/asm.rs | 1 - 2 files changed, 219 deletions(-) delete mode 100644 vm/examples/circle.asm diff --git a/vm/examples/circle.asm b/vm/examples/circle.asm deleted file mode 100644 index 859d1c4..0000000 --- a/vm/examples/circle.asm +++ /dev/null @@ -1,218 +0,0 @@ -# Data section -.data; - -# 800 * 600 * 4 (RGBX) -PIXEL_BUFFER: -.zero 1_920_000; - -WINDOW_TITLE: -.stringz "UVM Circle Animation Example"; - -# Global x coordinate variable -.align 8; -X_COORD: -.u64 0; - -########################################################### - -# Code section -.code; - -# Create a window -push 800; -push 600; -push WINDOW_TITLE; -push 0; -syscall window_create; - -push 100; -push ANIM_CALLBACK; -syscall time_delay_cb; - -push 0; -push PIXEL_BUFFER; -syscall window_draw_frame; - -# Wait for an event -push 0; -ret; - -########################################################### - -# Animation callback -ANIM_CALLBACK: - -# x: local 0 -push X_COORD; -load_u64; - -# x = x + dx -get_local 0; -push 10; -add_u64; -set_local 0; - -# x % 800 -get_local 0; -push 800; -mod_i64; -set_local 0; - -# update global x variable -push X_COORD; -get_local 0; -store_u64; - -# Clear the screen -push PIXEL_BUFFER; -push 0; -push 1_440_000; -syscall memset; - -# Draw the circle -get_local 0; -push 300; -push 20; -call DRAW_CIRCLE, 3; -pop; - -push 0; -push PIXEL_BUFFER; -syscall window_draw_frame; - -# Schedule the animation callback again -push 25; -push ANIM_CALLBACK; -syscall time_delay_cb; - -push 0; -ret; - -########################################################### - -# Draw a circle -# DRAW_CIRCLE(x, y, r) -DRAW_CIRCLE: - -# Local 0 -# xmin = x - r -get_arg 0; -get_arg 2; -sub_u64; - -# Local 1 -# xmax = x + r -get_arg 0; -get_arg 2; -add_u64; - -# Local 2 -# ymin = y - r -get_arg 1; -get_arg 2; -sub_u64; - -# Local 3 -# ymax = y + r -get_arg 1; -get_arg 2; -add_u64; - -# Local 4: x -# x = 0 -push 0; - -# Local 5: y -# y = ymin -get_local 2; - -# For each row -LOOP_Y: - - # For each column - # x = xmin - get_local 0; - set_local 4; - LOOP_X: - - # (x - xin)^2 - get_local 4; - get_arg 0; - sub_u64; - dup; - mul_u64; - - # (y - yin)^2 - get_local 5; - get_arg 1; - sub_u64; - dup; - mul_u64; - - # dx^2 + dy^2 - add_u64; - - # r^2 - get_arg 2; - dup; - mul_u64; - - # dx^2 + dy^2 < r^2 - lt_i64; - jz OUTSIDE_CIRCLE; - - get_local 4; - get_local 5; - call SET_PIXEL, 2; - pop; - - OUTSIDE_CIRCLE: - - # x = x + 2 - get_local 4; - push 1; - add_u64; - set_local 4; - - # while (x < xmax) - get_local 4; - get_local 1; - lt_i64; - jnz LOOP_X; - -# y = y + 1 -get_local 5; -push 1; -add_u64; -set_local 5; - -# while (y < ymax) -get_local 5; -get_local 3; -lt_i64; -jnz LOOP_Y; - -push 0; -ret; - -########################################################### - -# Set a pixel red -# SET_PIXEL(x, y) -SET_PIXEL: - -# Compute the pixel's address -# 800 * 4 * y + 4 * x -push 3200; -get_arg 1; -mul_u64; -get_arg 0; -push 4; -mul_u64; -add_u64; - -push 0xFF_00_00; -store_u32; - -push 0; -ret; diff --git a/vm/src/asm.rs b/vm/src/asm.rs index 226b933..9935c0c 100644 --- a/vm/src/asm.rs +++ b/vm/src/asm.rs @@ -1378,6 +1378,5 @@ mod tests parse_file("examples/loop.asm"); parse_file("examples/memcpy.asm"); parse_file("examples/gradient.asm"); - parse_file("examples/circle.asm"); } } From 947b3a95d17ee96c10f1d90c929388b2f5509db0 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 12:29:23 -0400 Subject: [PATCH 54/80] Fix gradient.asm example --- vm/examples/gradient.asm | 24 +++++++++++++++--------- vm/src/asm.rs | 3 +++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/vm/examples/gradient.asm b/vm/examples/gradient.asm index d0ce939..0033e7d 100644 --- a/vm/examples/gradient.asm +++ b/vm/examples/gradient.asm @@ -5,6 +5,9 @@ PIXEL_BUFFER: .zero 1_920_000; +EVENT: +.zero 256; + WINDOW_TITLE: .stringz "UVM Gradient Example"; @@ -118,14 +121,17 @@ push 0; push PIXEL_BUFFER; syscall window_draw_frame; - - -#syscall window_wait_event; - - - - - -# Return to the event loop +# Wait until the user closes the window +WAIT_FOR_EVENT: +push EVENT; +syscall window_wait_event; +push EVENT; +load_u16; +# Check for EVENT_QUIT +push_u32 0; +eq_u64; +jz WAIT_FOR_EVENT; + +# End program push 0; ret; diff --git a/vm/src/asm.rs b/vm/src/asm.rs index 9935c0c..566b9f8 100644 --- a/vm/src/asm.rs +++ b/vm/src/asm.rs @@ -514,6 +514,9 @@ impl Assembler } } + // TODO: + // Populate the available constants + Self { const_map: HashMap::new(), syscall_map: syscall_map, From 236c0ff94db99065338cda13a5130f0d35e40219 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 12:42:57 -0400 Subject: [PATCH 55/80] Rename SysCallFn to HostFn --- ncc/examples/basic.c | 1 - vm/src/host.rs | 52 ++++++++++++++++++++++---------------------- vm/src/vm.rs | 20 ++++++++--------- 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/ncc/examples/basic.c b/ncc/examples/basic.c index 7b7ebb5..1a23b0c 100644 --- a/ncc/examples/basic.c +++ b/ncc/examples/basic.c @@ -91,7 +91,6 @@ void textinput(u64 window_id, char ch) void keydown(u64 window_id, u16 keycode) { - if(vm_status != VM_STATUS_DONE) { if(keycode == KEY_ESCAPE) vm_status = VM_STATUS_HALT; diff --git a/vm/src/host.rs b/vm/src/host.rs index 0bb2ff6..c406311 100644 --- a/vm/src/host.rs +++ b/vm/src/host.rs @@ -15,7 +15,7 @@ use crate::constants::*; /// Note: the in/out arg count should be fixed so /// that we can JIT syscalls efficiently #[derive(Copy, Clone)] -pub enum SysCallFn +pub enum HostFn { Fn0_0(fn(&mut Thread)), Fn0_1(fn(&mut Thread) -> Value), @@ -33,7 +33,7 @@ pub enum SysCallFn Fn4_1(fn(&mut Thread, a0: Value, a1: Value, a2: Value, a3: Value) -> Value), } -impl SysCallFn +impl HostFn { fn argc(&self) -> usize { @@ -87,38 +87,38 @@ pub fn get_sdl_context() -> &'static mut sdl2::Sdl } /// Get the syscall with a given index -pub fn get_syscall(const_idx: u16) -> SysCallFn +pub fn get_syscall(const_idx: u16) -> HostFn { match const_idx { // Core VM syscalls - VM_HEAP_SIZE => SysCallFn::Fn0_1(vm_heap_size), - VM_GROW_HEAP => SysCallFn::Fn1_1(vm_grow_heap), - MEMSET => SysCallFn::Fn3_0(memset), - MEMSET32 => SysCallFn::Fn3_0(memset32), - MEMCPY => SysCallFn::Fn3_0(memcpy), - MEMCMP => SysCallFn::Fn3_1(memcmp), - - THREAD_SPAWN => SysCallFn::Fn1_1(thread_spawn), - THREAD_JOIN => SysCallFn::Fn1_1(thread_join), - THREAD_ID => SysCallFn::Fn0_1(thread_id), - THREAD_SLEEP => SysCallFn::Fn1_0(thread_sleep), + VM_HEAP_SIZE => HostFn::Fn0_1(vm_heap_size), + VM_GROW_HEAP => HostFn::Fn1_1(vm_grow_heap), + MEMSET => HostFn::Fn3_0(memset), + MEMSET32 => HostFn::Fn3_0(memset32), + MEMCPY => HostFn::Fn3_0(memcpy), + MEMCMP => HostFn::Fn3_1(memcmp), + + THREAD_SPAWN => HostFn::Fn1_1(thread_spawn), + THREAD_JOIN => HostFn::Fn1_1(thread_join), + THREAD_ID => HostFn::Fn0_1(thread_id), + THREAD_SLEEP => HostFn::Fn1_0(thread_sleep), // Console I/O - PRINT_I64 => SysCallFn::Fn1_0(print_i64), - PRINT_F32 => SysCallFn::Fn1_0(print_f32), - PRINT_STR => SysCallFn::Fn1_0(print_str), - PRINT_ENDL => SysCallFn::Fn0_0(print_endl), - PUTCHAR => SysCallFn::Fn1_1(putchar), - GETCHAR => SysCallFn::Fn0_1(getchar), + PRINT_I64 => HostFn::Fn1_0(print_i64), + PRINT_F32 => HostFn::Fn1_0(print_f32), + PRINT_STR => HostFn::Fn1_0(print_str), + PRINT_ENDL => HostFn::Fn0_0(print_endl), + PUTCHAR => HostFn::Fn1_1(putchar), + GETCHAR => HostFn::Fn0_1(getchar), - TIME_CURRENT_MS => SysCallFn::Fn0_1(time_current_ms), + TIME_CURRENT_MS => HostFn::Fn0_1(time_current_ms), - WINDOW_CREATE => SysCallFn::Fn4_1(window_create), - WINDOW_DRAW_FRAME => SysCallFn::Fn2_0(window_draw_frame), - WINDOW_POLL_EVENT => SysCallFn::Fn1_1(window_poll_event), - WINDOW_WAIT_EVENT => SysCallFn::Fn1_0(window_wait_event), + WINDOW_CREATE => HostFn::Fn4_1(window_create), + WINDOW_DRAW_FRAME => HostFn::Fn2_0(window_draw_frame), + WINDOW_POLL_EVENT => HostFn::Fn1_1(window_poll_event), + WINDOW_WAIT_EVENT => HostFn::Fn1_0(window_wait_event), - AUDIO_OPEN_OUTPUT => SysCallFn::Fn4_1(audio_open_output), + AUDIO_OPEN_OUTPUT => HostFn::Fn4_1(audio_open_output), _ => panic!("unknown syscall \"{}\"", const_idx), } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 1510e3b..76c6593 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -1505,47 +1505,47 @@ impl Thread match syscall_fn { - SysCallFn::Fn0_0(fun) => { + HostFn::Fn0_0(fun) => { fun(self) } - SysCallFn::Fn0_1(fun) => { + HostFn::Fn0_1(fun) => { let v = fun(self); self.push(v); } - SysCallFn::Fn1_0(fun) => { + HostFn::Fn1_0(fun) => { let a0 = self.pop(); fun(self, a0) } - SysCallFn::Fn1_1(fun) => { + HostFn::Fn1_1(fun) => { let a0 = self.pop(); let v = fun(self, a0); self.push(v); } - SysCallFn::Fn2_0(fun) => { + HostFn::Fn2_0(fun) => { let a1 = self.pop(); let a0 = self.pop(); fun(self, a0, a1) } - SysCallFn::Fn2_1(fun) => { + HostFn::Fn2_1(fun) => { let a1 = self.pop(); let a0 = self.pop(); let v = fun(self, a0, a1); self.push(v); } - SysCallFn::Fn3_0(fun) => { + HostFn::Fn3_0(fun) => { let a2 = self.pop(); let a1 = self.pop(); let a0 = self.pop(); fun(self, a0, a1, a2) } - SysCallFn::Fn3_1(fun) => { + HostFn::Fn3_1(fun) => { let a2 = self.pop(); let a1 = self.pop(); let a0 = self.pop(); @@ -1553,7 +1553,7 @@ impl Thread self.push(v); } - SysCallFn::Fn4_0(fun) => { + HostFn::Fn4_0(fun) => { let a3 = self.pop(); let a2 = self.pop(); let a1 = self.pop(); @@ -1561,7 +1561,7 @@ impl Thread fun(self, a0, a1, a2, a3) } - SysCallFn::Fn4_1(fun) => { + HostFn::Fn4_1(fun) => { let a3 = self.pop(); let a2 = self.pop(); let a1 = self.pop(); From a405dcd7715b61a9dd308c6c153dc05695229808 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 13:03:54 -0400 Subject: [PATCH 56/80] Fix examples/wu_lines.c --- ncc/examples/wu_lines.c | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/ncc/examples/wu_lines.c b/ncc/examples/wu_lines.c index 3bc70a4..5027bd1 100644 --- a/ncc/examples/wu_lines.c +++ b/ncc/examples/wu_lines.c @@ -2,7 +2,7 @@ #include #include #include -#include +#include size_t FRAME_WIDTH = 400; size_t FRAME_HEIGHT = 400; @@ -296,14 +296,7 @@ void draw_wu_line_fourth_octant(u32* fb, u32 fb_width, u32 fb_height, u32 x0, u3 } } -void mousemove(u64 window_id, u64 new_x, u64 new_y) -{ - // Update the mouse position - pos_x = new_x; - pos_y = new_y; -} - -void anim_callback() +void draw() { // Grey background. memset(frame_buffer, 0x7f, sizeof(frame_buffer)); @@ -320,17 +313,32 @@ void anim_callback() draw_wu_line(frame_buffer, FRAME_WIDTH, FRAME_HEIGHT, h - pos_y, pos_x, w, 0, COLOR_YELLOW ); window_draw_frame(0, frame_buffer); - - time_delay_cb(10, anim_callback); } +Event event; + void main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "Wu Anti-Aliased Line Example", 0); - window_on_mousemove(0, mousemove); + draw(); - time_delay_cb(0, anim_callback); + for (;;) + { + window_wait_event(&event); - enable_event_loop(); + if (event.kind == EVENT_QUIT || (event.kind == EVENT_KEYDOWN && event.key == KEY_ESCAPE)) + { + exit(0); + } + + if (event.kind == EVENT_MOUSEMOVE) + { + // Update the mouse position + pos_x = event.x; + pos_y = event.y; + } + + draw(); + } } From ef23245d976662e2314d5c9ad05596c1dd741b06 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 13:06:18 -0400 Subject: [PATCH 57/80] Fix tests/graphics.c --- ncc/tests/graphics.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/ncc/tests/graphics.c b/ncc/tests/graphics.c index 94c542e..606c729 100644 --- a/ncc/tests/graphics.c +++ b/ncc/tests/graphics.c @@ -1,19 +1,13 @@ #include #include -#include +#include #include #include // Frame buffer u32 fb[800][600]; -void keydown(u64 window_id, u16 keycode) -{ - if (keycode == KEY_ESCAPE) - { - exit(0); - } -} +Event event; int main() { @@ -52,9 +46,13 @@ int main() // a window so we can view the output #ifndef TEST window_create(800, 600, "Graphics Test", 0); - window_on_keydown(0, keydown); window_draw_frame(0, fb); - enable_event_loop(); + for (;;) + { + window_wait_event(&event); + if (event.kind == EVENT_KEYDOWN) + break; + } #endif return 0; From 4fa504b88d7c2129dcb0cc7feabb8c5a2aaa7a12 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 13:16:04 -0400 Subject: [PATCH 58/80] Refactor chess.c for new event system --- ncc/examples/chess.c | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/ncc/examples/chess.c b/ncc/examples/chess.c index 14477c2..a91264c 100644 --- a/ncc/examples/chess.c +++ b/ncc/examples/chess.c @@ -7,7 +7,7 @@ // #include -#include +#include #include #include #include @@ -429,7 +429,7 @@ void computer_callback() X(0,0,0,21,u,1); } -void anim_callback() +void draw_board() { if (left_key) { left_key = false; @@ -473,7 +473,10 @@ void anim_callback() X(0,0,0,21,u,1); selected = 0; if (y != prev_turn) { - time_delay_cb(100, computer_callback); + // Draw the newly moved piece + draw_board(); + // Let the computer do its move + computer_callback(); } } } @@ -566,10 +569,9 @@ void anim_callback() } window_draw_frame(0, frame_buffer); - time_delay_cb(33, anim_callback); } -void keydown(u64 window_id, u16 keycode) +void keydown(u16 keycode) { if (keycode == KEY_LEFT) { @@ -593,7 +595,7 @@ void keydown(u64 window_id, u16 keycode) } } -void keyup(u64 window_id, u16 keycode) +void keyup(u16 keycode) { if (keycode == KEY_LEFT) { @@ -617,15 +619,35 @@ void keyup(u64 window_id, u16 keycode) } } +Event event; + void main() { init(); window_create(FRAME_WIDTH, FRAME_HEIGHT, "Toledo Nanochess for UVM", 0); - window_on_keydown(0, keydown); - window_on_keyup(0, keyup); - time_delay_cb(0, anim_callback); + draw_board(); + + for (;;) + { + window_wait_event(&event); - enable_event_loop(); + if (event.kind == EVENT_QUIT) + { + exit(0); + } + + if (event.kind == EVENT_KEYDOWN) + { + keydown(event.key); + } + + if (event.kind == EVENT_KEYUP) + { + keyup(event.key); + } + + draw_board(); + } } From 508a7c7e60c04311d3bebc0628399fa8f6228619 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 13:36:36 -0400 Subject: [PATCH 59/80] Handle TextInput event --- api/syscalls.json | 5 +++++ doc/syscalls.md | 1 + ncc/include/uvm/syscalls.h | 1 + ncc/include/uvm/window.h | 1 + vm/src/constants.rs | 1 + vm/src/window.rs | 23 +++++++++++++++++------ 6 files changed, 26 insertions(+), 6 deletions(-) diff --git a/api/syscalls.json b/api/syscalls.json index 779cf58..4a44cca 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -415,6 +415,11 @@ "u16", 5 ], + [ + "EVENT_TEXTINPUT", + "u16", + 6 + ], [ "KEY_BACKSPACE", "u16", diff --git a/doc/syscalls.md b/doc/syscalls.md index 0afb715..3c3f783 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -225,6 +225,7 @@ These are the constants associated with the window subsystem: - `u16 EVENT_MOUSEDOWN = 3` - `u16 EVENT_MOUSEUP = 4` - `u16 EVENT_MOUSEMOVE = 5` +- `u16 EVENT_TEXTINPUT = 6` - `u16 KEY_BACKSPACE = 8` - `u16 KEY_TAB = 9` - `u16 KEY_RETURN = 10` diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index aa624c3..5859b71 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -119,6 +119,7 @@ #define EVENT_MOUSEDOWN 3 #define EVENT_MOUSEUP 4 #define EVENT_MOUSEMOVE 5 +#define EVENT_TEXTINPUT 6 #define KEY_BACKSPACE 8 #define KEY_TAB 9 #define KEY_RETURN 10 diff --git a/ncc/include/uvm/window.h b/ncc/include/uvm/window.h index 0dc907a..f5df4fb 100644 --- a/ncc/include/uvm/window.h +++ b/ncc/include/uvm/window.h @@ -12,6 +12,7 @@ typedef struct u16 button; i32 x; i32 y; + char text[64]; } Event; // Stack allocation of structs not yet supported diff --git a/vm/src/constants.rs b/vm/src/constants.rs index 2fe3058..8e0e914 100644 --- a/vm/src/constants.rs +++ b/vm/src/constants.rs @@ -83,6 +83,7 @@ pub const EVENT_KEYUP: u16 = 2; pub const EVENT_MOUSEDOWN: u16 = 3; pub const EVENT_MOUSEUP: u16 = 4; pub const EVENT_MOUSEMOVE: u16 = 5; +pub const EVENT_TEXTINPUT: u16 = 6; pub const KEY_BACKSPACE: u16 = 8; pub const KEY_TAB: u16 = 9; pub const KEY_RETURN: u16 = 10; diff --git a/vm/src/window.rs b/vm/src/window.rs index 9514df5..964f2dd 100644 --- a/vm/src/window.rs +++ b/vm/src/window.rs @@ -156,6 +156,8 @@ pub fn window_draw_frame(thread: &mut Thread, window_id: Value, src_addr: Value) window.canvas.present(); } +const EVENT_TEXT_MAX_BYTES: usize = 64; + // C event struct #[repr(C)] struct CEvent @@ -166,6 +168,7 @@ struct CEvent button: u16, x: i32, y: i32, + text: [u8; EVENT_TEXT_MAX_BYTES], } /// Takes a pointer ot an event struct to write into @@ -281,16 +284,24 @@ fn translate_event(sdl_event: Event, c_event: &mut CEvent) -> bool true } - /* Event::TextInput { window_id, text, .. } => { + c_event.kind = EVENT_TEXTINPUT; + c_event.window_id = 0; + + let text_bytes = text.bytes(); + + // This should never happen + if text_bytes.len() > EVENT_TEXT_MAX_BYTES { + panic!(); + } + // For each UTF-8 byte of input - for ch in text.bytes() { - if let ExitReason::Exit(val) = window_call_textinput(vm, window_id, ch) { - return ExitReason::Exit(val); - } + for (i, ch) in text_bytes.enumerate() { + c_event.text[i] = ch; } + + true } - */ _ => false } From 40159931521cc606a03e69420f63d8acd47138cb Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 13:47:12 -0400 Subject: [PATCH 60/80] Fix examples/textedit.c --- ncc/examples/textedit.c | 42 +++++++++++++++++++++++++++++------------ vm/src/window.rs | 3 ++- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/ncc/examples/textedit.c b/ncc/examples/textedit.c index ce3cf9a..6734506 100644 --- a/ncc/examples/textedit.c +++ b/ncc/examples/textedit.c @@ -1,6 +1,8 @@ #include +#include #include #include +#include #include #include @@ -30,7 +32,7 @@ size_t row_len(size_t row_idx) return NUM_COLS; } -void textinput(u64 window_id, char ch) +void textinput(char ch) { //print_i64(ch); //print_endl(); @@ -50,7 +52,7 @@ void textinput(u64 window_id, char ch) redraw(); } -void keydown(u64 window_id, u16 keycode) +void keydown(u16 keycode) { if (keycode == KEY_ESCAPE) { @@ -131,12 +133,7 @@ void redraw() window_draw_frame(0, frame_buffer); } -void anim_callback() -{ - benchmark(redraw()); - - time_delay_cb(400, anim_callback); -} +Event event; void main() { @@ -144,12 +141,33 @@ void main() redraw(); - window_on_keydown(0, keydown); - window_on_textinput(0, textinput); + for (;;) + { + while (window_poll_event(&event)) + { + if (event.kind == EVENT_QUIT) + { + exit(0); + } + + if (event.kind == EVENT_KEYDOWN) + { + keydown(event.key); + } - time_delay_cb(0, anim_callback); + if (event.kind == EVENT_TEXTINPUT) + { + size_t len = strlen(event.text); + for (size_t i = 0; i < len; ++i) + { + textinput(event.text[i]); + } + } + } - enable_event_loop(); + benchmark(redraw()); + thread_sleep(20); + } } //=========================================================================== diff --git a/vm/src/window.rs b/vm/src/window.rs index 964f2dd..8c53720 100644 --- a/vm/src/window.rs +++ b/vm/src/window.rs @@ -287,11 +287,12 @@ fn translate_event(sdl_event: Event, c_event: &mut CEvent) -> bool Event::TextInput { window_id, text, .. } => { c_event.kind = EVENT_TEXTINPUT; c_event.window_id = 0; + c_event.text.fill(0); let text_bytes = text.bytes(); // This should never happen - if text_bytes.len() > EVENT_TEXT_MAX_BYTES { + if text_bytes.len() > EVENT_TEXT_MAX_BYTES - 1 { panic!(); } From 86eb2c84b479a8218acdcb7c407cf8ee358d6aad Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 14:06:12 -0400 Subject: [PATCH 61/80] Remove basic.c example for now Complexity makes it hard to test and maintain. --- ncc/examples/basic.c | 2165 ------------------------------------------ 1 file changed, 2165 deletions(-) delete mode 100644 ncc/examples/basic.c diff --git a/ncc/examples/basic.c b/ncc/examples/basic.c deleted file mode 100644 index 1a23b0c..0000000 --- a/ncc/examples/basic.c +++ /dev/null @@ -1,2165 +0,0 @@ -// -// UVM Basic -// by Abdul Bahajaj -// -// Note to reader: When I authored this VM there were a lot of missing features from the NCC compiler ( e.g. there are no structs, unions, etc...). -// Keeping this in mind might help you understand why the code is structured in the way it is. - - -#include -#include -#include -#include -#include -#include -#include -#include - -// VM status -#define VM_STATUS_RUNNING 0 -#define VM_STATUS_DONE 1 -#define VM_STATUS_HALT 2 -#define FONT_MONOGRAM_NUMBER_OF_CHARACTERS 390 -#define FONT_MONOGRAM_HEIGHT 12 -#define FONT_MONOGRAM_WIDTH 7 - -#define FRAME_WIDTH 1200 -#define FRAME_HEIGHT 700 -#define NUM_COLS 25 -#define NUM_ROWS 21 - -/* #define DEBUG */ -#ifdef DEBUG -#define DEBUGS(s) puts(__FILE__ " : "); print_i64(__LINE__); puts(" "); puts(s); -#define DEBUGI(i) print_i64(i); -#else -#define DEBUGS(s) -#define DEBUGI(i) -#endif -#define DEBUG(s) DEBUGS(s "\n"); - -#define TRY(exp) if(exp) return 1; -#define NULLGAURD(ptr) if(ptr == NULL) return NULL; - -size_t console_width; -size_t margin = 5; -size_t char_width = 18; -size_t char_height = 32; -u32 frame_buffer[FRAME_HEIGHT][FRAME_WIDTH]; -char text[NUM_ROWS][NUM_COLS]; - -// Position of the cursor -#define MIN_COL_FIRST_LINE 2 -size_t line_idx = 0; -size_t min_line_idx = 4; -size_t col_idx = 0; -size_t min_col_idx; - -size_t row_len(size_t row_idx) -{ - for (int i = 0; i < NUM_COLS; ++i) - { - if (text[row_idx][i] == 0) - { - return i; - } - } - return NUM_COLS; -} - -u32* get_point_ptr(u32* dst, size_t frame_width, size_t x, size_t y) -{ - return dst + frame_width* y + x; -} - -int white = 0xFFFFFF; -int blue = 0x0247fe; -int red = 0xFF0000; -int green = 0x008000; -int black = 0x0; - -void textinput(u64 window_id, char ch) -{ - if(vm_status != VM_STATUS_DONE) return; - console_putchar(ch); - vm_command_text_buffer[vm_command_text_buffer_cursor] = ch; - DEBUGS(vm_command_text_buffer); - DEBUGS("\n"); - ++vm_command_text_buffer_cursor; - console_redraw_commit(); -} - -void keydown(u64 window_id, u16 keycode) -{ - if(vm_status != VM_STATUS_DONE) - { - if(keycode == KEY_ESCAPE) vm_status = VM_STATUS_HALT; - return; - } - if (keycode == KEY_ESCAPE) exit(0); - else if (keycode == KEY_BACKSPACE) - { - if (col_idx > min_col_idx) - { - col_idx = col_idx - 1; - vm_command_text_buffer_backspace(); - } - else if (line_idx > min_line_idx) - { - console_redraw_line(line_idx); - line_idx = line_idx - 1; - if(line_idx == min_line_idx) min_col_idx = MIN_COL_FIRST_LINE; - col_idx = row_len(line_idx); - } - text[line_idx][col_idx] = 0; - } - else if (keycode == KEY_RETURN) - { - vm_load_cmd(); - vm_exec(); - } - console_redraw_commit(); -} - -void anim_callback() -{ - console_redraw_commit(); - /* benchmark(); */ - - time_delay_cb(400, anim_callback); -} - -void console_print_ready() -{ - console_newline(); - console_puts("READY."); - console_newline(); - console_puts("> "); - min_col_idx = MIN_COL_FIRST_LINE; -} - -void main() -{ - DEBUGS("DEBUGGING\n"); - console_width = FRAME_WIDTH/3+75; - vm_init(); - vm_command_text_buffer_clear(); - window_create(FRAME_WIDTH, FRAME_HEIGHT, "UVM Basic", 0); - - canvas_fill(white); - console_redraw_all_text(); - - console_puts(" **** UVM Basic ****"); - console_newline(); - console_puts("Type HELP for available "); - console_newline(); - console_puts("commands"); - console_print_ready(); - - console_redraw_commit(); - - window_on_keydown(0, keydown); - window_on_textinput(0, textinput); - - time_delay_cb(0, anim_callback); - - enable_event_loop(); -} - -//=========================================================================== -/* CONSOLE */ - -// This is 12 bytes of data per char, as in 12 rows, and each byte -// represents the pixels in that row. -u8 font_monogram_data[FONT_MONOGRAM_NUMBER_OF_CHARACTERS][FONT_MONOGRAM_HEIGHT] = { - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x19, 0x15, 0x13, 0x11, 0x0e, 0x00, 0x00, }, // '0' - { 0x00, 0x00, 0x00, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // '1' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x10, 0x08, 0x04, 0x02, 0x1f, 0x00, 0x00, }, // '2' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x10, 0x0c, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // '3' - { 0x00, 0x00, 0x00, 0x12, 0x12, 0x11, 0x1f, 0x10, 0x10, 0x10, 0x00, 0x00, }, // '4' - { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x0f, 0x10, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // '5' - { 0x00, 0x00, 0x00, 0x0e, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // '6' - { 0x00, 0x00, 0x00, 0x1f, 0x10, 0x10, 0x08, 0x04, 0x04, 0x04, 0x00, 0x00, }, // '7' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x0e, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // '8' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x1e, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // '9' - { 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, }, // '!' - { 0x00, 0x00, 0x00, 0x0a, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '"' - { 0x00, 0x00, 0x00, 0x00, 0x0a, 0x1f, 0x0a, 0x0a, 0x1f, 0x0a, 0x00, 0x00, }, // '#' - { 0x00, 0x00, 0x00, 0x04, 0x1e, 0x05, 0x0e, 0x14, 0x0f, 0x04, 0x00, 0x00, }, // '$' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x08, 0x04, 0x02, 0x11, 0x11, 0x00, 0x00, }, // '%' - { 0x00, 0x00, 0x00, 0x06, 0x09, 0x09, 0x1e, 0x09, 0x09, 0x16, 0x00, 0x00, }, // '&' - { 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // "'" - { 0x00, 0x00, 0x00, 0x08, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x00, 0x00, }, // '(' - { 0x00, 0x00, 0x00, 0x02, 0x04, 0x04, 0x04, 0x04, 0x04, 0x02, 0x00, 0x00, }, // ')' - { 0x00, 0x00, 0x00, 0x00, 0x04, 0x15, 0x0e, 0x15, 0x04, 0x00, 0x00, 0x00, }, // '*' - { 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x1f, 0x04, 0x04, 0x00, 0x00, 0x00, }, // '+' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x02, 0x00, }, // ',' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '-' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, }, // '.' - { 0x00, 0x00, 0x00, 0x10, 0x10, 0x08, 0x04, 0x02, 0x01, 0x01, 0x00, 0x00, }, // '/' - { 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, }, // ':' - { 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04, 0x02, 0x00, }, // ';' - { 0x00, 0x00, 0x00, 0x00, 0x18, 0x06, 0x01, 0x06, 0x18, 0x00, 0x00, 0x00, }, // '<' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, }, // '=' - { 0x00, 0x00, 0x00, 0x00, 0x03, 0x0c, 0x10, 0x0c, 0x03, 0x00, 0x00, 0x00, }, // '>' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x10, 0x08, 0x04, 0x00, 0x04, 0x00, 0x00, }, // '?' - { 0x00, 0x00, 0x00, 0x0e, 0x19, 0x15, 0x15, 0x19, 0x01, 0x0e, 0x00, 0x00, }, // '@' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x00, 0x00, }, // 'A' - { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'B' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'C' - { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'D' - { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'E' - { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'F' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x1d, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'G' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'H' - { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'I' - { 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'J' - { 0x00, 0x00, 0x00, 0x11, 0x09, 0x05, 0x03, 0x05, 0x09, 0x11, 0x00, 0x00, }, // 'K' - { 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'L' - { 0x00, 0x00, 0x00, 0x11, 0x1b, 0x15, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'M' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x00, 0x00, }, // 'N' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'O' - { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'P' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x18, 0x00, }, // 'Q' - { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'R' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x0e, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // 'S' - { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'T' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'U' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0a, 0x0a, 0x04, 0x00, 0x00, }, // 'V' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x15, 0x1b, 0x11, 0x00, 0x00, }, // 'W' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x11, 0x00, 0x00, }, // 'X' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Y' - { 0x00, 0x00, 0x00, 0x1f, 0x10, 0x08, 0x04, 0x02, 0x01, 0x1f, 0x00, 0x00, }, // 'Z' - { 0x00, 0x00, 0x00, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0c, 0x00, 0x00, }, // '[' - { 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x04, 0x08, 0x10, 0x10, 0x00, 0x00, }, // '\\' - { 0x00, 0x00, 0x00, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x06, 0x00, 0x00, }, // ']' - { 0x00, 0x00, 0x00, 0x04, 0x0a, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '^' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, }, // '_' - { 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '`' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'a' - { 0x00, 0x00, 0x00, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'b' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'c' - { 0x00, 0x00, 0x00, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'd' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'e' - { 0x00, 0x00, 0x00, 0x0c, 0x12, 0x02, 0x0f, 0x02, 0x02, 0x02, 0x00, 0x00, }, // 'f' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'g' - { 0x00, 0x00, 0x00, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'h' - { 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'i' - { 0x00, 0x00, 0x00, 0x10, 0x00, 0x18, 0x10, 0x10, 0x10, 0x10, 0x11, 0x0e, }, // 'j' - { 0x00, 0x00, 0x00, 0x01, 0x01, 0x11, 0x09, 0x07, 0x09, 0x11, 0x00, 0x00, }, // 'k' - { 0x00, 0x00, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x1c, 0x00, 0x00, }, // 'l' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x15, 0x15, 0x15, 0x15, 0x00, 0x00, }, // 'm' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'n' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'o' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x01, }, // 'p' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x10, }, // 'q' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x13, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'r' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x01, 0x0e, 0x10, 0x0f, 0x00, 0x00, }, // 's' - { 0x00, 0x00, 0x00, 0x02, 0x02, 0x0f, 0x02, 0x02, 0x02, 0x1c, 0x00, 0x00, }, // 't' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'u' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x0a, 0x04, 0x00, 0x00, }, // 'v' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0a, 0x00, 0x00, }, // 'w' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x00, 0x00, }, // 'x' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'y' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x08, 0x04, 0x02, 0x1f, 0x00, 0x00, }, // 'z' - { 0x00, 0x00, 0x00, 0x08, 0x04, 0x04, 0x02, 0x04, 0x04, 0x08, 0x00, 0x00, }, // '{' - { 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // '|' - { 0x00, 0x00, 0x00, 0x02, 0x04, 0x04, 0x08, 0x04, 0x04, 0x02, 0x00, 0x00, }, // '}' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '~' - { 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // '¡' - { 0x00, 0x00, 0x00, 0x04, 0x0e, 0x15, 0x05, 0x15, 0x0e, 0x04, 0x00, 0x00, }, // '¢' - { 0x00, 0x00, 0x00, 0x0c, 0x12, 0x02, 0x0f, 0x02, 0x02, 0x1f, 0x00, 0x00, }, // '£' - { 0x00, 0x00, 0x00, 0x00, 0x11, 0x0e, 0x0a, 0x0e, 0x11, 0x00, 0x00, 0x00, }, // '¤' - { 0x00, 0x00, 0x00, 0x11, 0x0a, 0x04, 0x1f, 0x04, 0x1f, 0x04, 0x00, 0x00, }, // '¥' - { 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, }, // '¦' - { 0x00, 0x00, 0x00, 0x1e, 0x01, 0x0e, 0x11, 0x0e, 0x10, 0x0f, 0x00, 0x00, }, // '§' - { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '¨' - { 0x00, 0x00, 0x00, 0x0e, 0x1b, 0x15, 0x1d, 0x15, 0x1b, 0x0e, 0x00, 0x00, }, // '©' - { 0x00, 0x00, 0x00, 0x0e, 0x09, 0x09, 0x09, 0x0e, 0x00, 0x00, 0x00, 0x00, }, // 'ª' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x09, 0x12, 0x00, 0x00, 0x00, 0x00, }, // '«' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x10, 0x00, 0x00, 0x00, 0x00, }, // '¬' - { 0x00, 0x00, 0x00, 0x0e, 0x19, 0x15, 0x15, 0x19, 0x15, 0x0e, 0x00, 0x00, }, // '®' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '¯' - { 0x00, 0x00, 0x00, 0x06, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '°' - { 0x00, 0x00, 0x00, 0x04, 0x04, 0x1f, 0x04, 0x04, 0x00, 0x1f, 0x00, 0x00, }, // '±' - { 0x00, 0x00, 0x00, 0x03, 0x04, 0x02, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, }, // '²' - { 0x00, 0x00, 0x00, 0x03, 0x04, 0x02, 0x04, 0x03, 0x00, 0x00, 0x00, 0x00, }, // '³' - { 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '´' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x01, }, // 'µ' - { 0x00, 0x00, 0x00, 0x1e, 0x17, 0x17, 0x17, 0x16, 0x14, 0x14, 0x00, 0x00, }, // '¶' - { 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '·' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x06, }, // '¸' - { 0x00, 0x00, 0x00, 0x02, 0x03, 0x02, 0x02, 0x07, 0x00, 0x00, 0x00, 0x00, }, // '¹' - { 0x00, 0x00, 0x00, 0x06, 0x09, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00, 0x00, }, // 'º' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x12, 0x09, 0x00, 0x00, 0x00, 0x00, }, // '»' - { 0x00, 0x00, 0x00, 0x01, 0x09, 0x05, 0x02, 0x15, 0x1c, 0x10, 0x00, 0x00, }, // '¼' - { 0x00, 0x00, 0x00, 0x01, 0x09, 0x05, 0x0e, 0x11, 0x08, 0x1c, 0x00, 0x00, }, // '½' - { 0x00, 0x00, 0x00, 0x07, 0x16, 0x0f, 0x04, 0x16, 0x1d, 0x10, 0x00, 0x00, }, // '¾' - { 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x02, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // '¿' - { 0x02, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'À' - { 0x08, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Á' - { 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Â' - { 0x16, 0x09, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ã' - { 0x00, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ä' - { 0x04, 0x0a, 0x04, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Å' - { 0x00, 0x00, 0x00, 0x1e, 0x05, 0x05, 0x1f, 0x05, 0x05, 0x1d, 0x00, 0x00, }, // 'Æ' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x08, 0x06, }, // 'Ç' - { 0x02, 0x04, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'È' - { 0x08, 0x04, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'É' - { 0x04, 0x0a, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ê' - { 0x00, 0x0a, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ë' - { 0x02, 0x04, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Ì' - { 0x08, 0x04, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Í' - { 0x04, 0x0a, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Î' - { 0x00, 0x0a, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Ï' - { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x13, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'Ð' - { 0x16, 0x09, 0x00, 0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x00, 0x00, }, // 'Ñ' - { 0x02, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ò' - { 0x08, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ó' - { 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ô' - { 0x16, 0x09, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Õ' - { 0x00, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ö' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x04, 0x0a, 0x00, 0x00, 0x00, }, // '×' - { 0x00, 0x00, 0x00, 0x16, 0x09, 0x19, 0x15, 0x13, 0x12, 0x0d, 0x00, 0x00, }, // 'Ø' - { 0x02, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ù' - { 0x08, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ú' - { 0x04, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Û' - { 0x00, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ü' - { 0x08, 0x04, 0x00, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Ý' - { 0x00, 0x00, 0x00, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x00, 0x00, }, // 'Þ' - { 0x00, 0x00, 0x00, 0x06, 0x09, 0x09, 0x0d, 0x11, 0x11, 0x0d, 0x00, 0x00, }, // 'ß' - { 0x00, 0x00, 0x02, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'à' - { 0x00, 0x00, 0x08, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'á' - { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'â' - { 0x00, 0x00, 0x16, 0x09, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ã' - { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ä' - { 0x00, 0x04, 0x0a, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'å' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x15, 0x1d, 0x05, 0x1e, 0x00, 0x00, }, // 'æ' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x08, 0x06, }, // 'ç' - { 0x00, 0x00, 0x02, 0x04, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'è' - { 0x00, 0x00, 0x08, 0x04, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'é' - { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ê' - { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ë' - { 0x00, 0x00, 0x02, 0x04, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ì' - { 0x00, 0x00, 0x08, 0x04, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'í' - { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'î' - { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ï' - { 0x00, 0x00, 0x0e, 0x30, 0x18, 0x1e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ð' - { 0x00, 0x00, 0x16, 0x09, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'ñ' - { 0x00, 0x00, 0x02, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ò' - { 0x00, 0x00, 0x08, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ó' - { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ô' - { 0x00, 0x00, 0x16, 0x09, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'õ' - { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ö' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x04, 0x00, 0x00, }, // '÷' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x09, 0x15, 0x12, 0x0d, 0x00, 0x00, }, // 'ø' - { 0x00, 0x00, 0x02, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ù' - { 0x00, 0x00, 0x08, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ú' - { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'û' - { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ü' - { 0x00, 0x00, 0x08, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ý' - { 0x00, 0x00, 0x00, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x01, }, // 'þ' - { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ÿ' - { 0x00, 0x0e, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ā' - { 0x00, 0x00, 0x00, 0x0e, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ā' - { 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ă' - { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ă' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x08, 0x10, }, // 'Ą' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x04, 0x18, }, // 'ą' - { 0x08, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'Ć' - { 0x00, 0x00, 0x08, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'ć' - { 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'Ĉ' - { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'ĉ' - { 0x00, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'Ċ' - { 0x00, 0x00, 0x00, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'ċ' - { 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'Č' - { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'č' - { 0x0a, 0x04, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'Ď' - { 0x00, 0x00, 0x50, 0x50, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ď' - { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x13, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'Đ' - { 0x00, 0x00, 0x10, 0x3c, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'đ' - { 0x00, 0x0e, 0x00, 0x1f, 0x01, 0x01, 0x07, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ē' - { 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ē' - { 0x0a, 0x04, 0x00, 0x1f, 0x01, 0x01, 0x07, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ĕ' - { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ĕ' - { 0x00, 0x04, 0x00, 0x1f, 0x01, 0x01, 0x07, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ė' - { 0x00, 0x00, 0x00, 0x04, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ė' - { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x07, 0x01, 0x01, 0x1f, 0x04, 0x18, }, // 'Ę' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x1e, 0x04, 0x18, }, // 'ę' - { 0x00, 0x0e, 0x00, 0x1f, 0x01, 0x01, 0x07, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ě' - { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ě' - { 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x01, 0x1d, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ĝ' - { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ĝ' - { 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x1d, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ğ' - { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ğ' - { 0x00, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x1d, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ġ' - { 0x00, 0x00, 0x00, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ġ' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x1d, 0x11, 0x11, 0x0e, 0x08, 0x06, }, // 'Ģ' - { 0x00, 0x00, 0x08, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ģ' - { 0x04, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ĥ' - { 0x00, 0x00, 0x08, 0x15, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'ĥ' - { 0x00, 0x00, 0x00, 0x11, 0x3f, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ħ' - { 0x00, 0x00, 0x00, 0x01, 0x03, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'ħ' - { 0x16, 0x09, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Ĩ' - { 0x00, 0x00, 0x16, 0x09, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ĩ' - { 0x00, 0x0e, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Ī' - { 0x00, 0x00, 0x00, 0x0e, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ī' - { 0x0a, 0x04, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Ĭ' - { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ĭ' - { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x04, 0x18, }, // 'Į' - { 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x04, 0x18, }, // 'į' - { 0x16, 0x09, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'İ' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ı' - { 0x00, 0x00, 0x00, 0x17, 0x12, 0x12, 0x12, 0x12, 0x12, 0x0f, 0x00, 0x00, }, // 'IJ' - { 0x00, 0x00, 0x00, 0x12, 0x00, 0x1b, 0x12, 0x12, 0x12, 0x1f, 0x10, 0x0e, }, // 'ij' - { 0x04, 0x0a, 0x00, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ĵ' - { 0x00, 0x00, 0x10, 0x28, 0x00, 0x18, 0x10, 0x10, 0x10, 0x10, 0x11, 0x0e, }, // 'ĵ' - { 0x00, 0x00, 0x00, 0x11, 0x09, 0x05, 0x03, 0x05, 0x09, 0x11, 0x04, 0x04, }, // 'Ķ' - { 0x00, 0x00, 0x00, 0x01, 0x01, 0x11, 0x09, 0x07, 0x09, 0x11, 0x04, 0x04, }, // 'ķ' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x09, 0x07, 0x09, 0x11, 0x00, 0x00, }, // 'ĸ' - { 0x08, 0x04, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ĺ' - { 0x08, 0x04, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ĺ' - { 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x08, 0x06, }, // 'Ļ' - { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x08, 0x06, }, // 'ļ' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x09, 0x01, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ľ' - { 0x00, 0x00, 0x00, 0x13, 0x12, 0x0a, 0x02, 0x02, 0x02, 0x1c, 0x00, 0x00, }, // 'ľ' - { 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x09, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ŀ' - { 0x00, 0x00, 0x00, 0x03, 0x02, 0x02, 0x0a, 0x02, 0x02, 0x1c, 0x00, 0x00, }, // 'ŀ' - { 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ł' - { 0x00, 0x00, 0x00, 0x03, 0x02, 0x02, 0x06, 0x03, 0x02, 0x1c, 0x00, 0x00, }, // 'ł' - { 0x08, 0x04, 0x00, 0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x00, 0x00, }, // 'Ń' - { 0x00, 0x00, 0x08, 0x04, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'ń' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x04, 0x03, }, // 'Ņ' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x04, 0x03, }, // 'ņ' - { 0x0a, 0x04, 0x00, 0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x00, 0x00, }, // 'Ň' - { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'ň' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'ʼn' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x10, 0x0c, }, // 'Ŋ' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x10, 0x0c, }, // 'ŋ' - { 0x00, 0x0e, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ō' - { 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ō' - { 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ŏ' - { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ŏ' - { 0x14, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ő' - { 0x00, 0x00, 0x14, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ő' - { 0x00, 0x00, 0x00, 0x1e, 0x05, 0x05, 0x1d, 0x05, 0x05, 0x1e, 0x00, 0x00, }, // 'Œ' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x15, 0x1d, 0x05, 0x0e, 0x00, 0x00, }, // 'œ' - { 0x08, 0x04, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ŕ' - { 0x00, 0x00, 0x08, 0x04, 0x00, 0x0d, 0x13, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'ŕ' - { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x11, 0x11, 0x11, 0x04, 0x03, }, // 'Ŗ' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x13, 0x01, 0x01, 0x01, 0x04, 0x03, }, // 'ŗ' - { 0x0a, 0x04, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ř' - { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x0d, 0x13, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'ř' - { 0x08, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x0e, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // 'Ś' - { 0x00, 0x00, 0x08, 0x04, 0x00, 0x1e, 0x01, 0x0e, 0x10, 0x0f, 0x00, 0x00, }, // 'ś' - { 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x01, 0x0e, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // 'Ŝ' - { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x1e, 0x01, 0x0e, 0x10, 0x0f, 0x00, 0x00, }, // 'ŝ' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x0e, 0x10, 0x11, 0x0e, 0x04, 0x03, }, // 'Ş' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x01, 0x0e, 0x10, 0x0f, 0x04, 0x03, }, // 'ş' - { 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x0e, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // 'Š' - { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x1e, 0x01, 0x0e, 0x10, 0x0f, 0x00, 0x00, }, // 'š' - { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, }, // 'Ţ' - { 0x00, 0x00, 0x00, 0x02, 0x02, 0x0f, 0x02, 0x02, 0x02, 0x1c, 0x08, 0x06, }, // 'ţ' - { 0x0a, 0x04, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Ť' - { 0x00, 0x00, 0x08, 0x0a, 0x02, 0x0f, 0x02, 0x02, 0x02, 0x1c, 0x00, 0x00, }, // 'ť' - { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x0e, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Ŧ' - { 0x00, 0x00, 0x00, 0x02, 0x0f, 0x02, 0x0f, 0x02, 0x02, 0x1c, 0x00, 0x00, }, // 'ŧ' - { 0x16, 0x09, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ũ' - { 0x00, 0x00, 0x16, 0x09, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ũ' - { 0x00, 0x0e, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ū' - { 0x00, 0x00, 0x00, 0x0e, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ū' - { 0x0a, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ŭ' - { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ŭ' - { 0x0a, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ů' - { 0x00, 0x04, 0x0a, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ů' - { 0x14, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ű' - { 0x00, 0x00, 0x14, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ű' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x04, 0x18, }, // 'Ų' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x04, 0x18, }, // 'ų' - { 0x04, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x15, 0x1b, 0x11, 0x00, 0x00, }, // 'Ŵ' - { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0a, 0x00, 0x00, }, // 'ŵ' - { 0x04, 0x0a, 0x00, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Ŷ' - { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ŷ' - { 0x00, 0x0a, 0x00, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Ÿ' - { 0x08, 0x04, 0x00, 0x1f, 0x10, 0x08, 0x04, 0x02, 0x01, 0x1f, 0x00, 0x00, }, // 'Ź' - { 0x00, 0x00, 0x08, 0x04, 0x00, 0x1f, 0x08, 0x04, 0x02, 0x1f, 0x00, 0x00, }, // 'ź' - { 0x00, 0x04, 0x00, 0x1f, 0x10, 0x08, 0x04, 0x02, 0x01, 0x1f, 0x00, 0x00, }, // 'Ż' - { 0x00, 0x00, 0x00, 0x04, 0x00, 0x1f, 0x08, 0x04, 0x02, 0x1f, 0x00, 0x00, }, // 'ż' - { 0x0a, 0x04, 0x00, 0x1f, 0x10, 0x08, 0x04, 0x02, 0x01, 0x1f, 0x00, 0x00, }, // 'Ž' - { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x1f, 0x08, 0x04, 0x02, 0x1f, 0x00, 0x00, }, // 'ž' - { 0x00, 0x0a, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ё' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x00, 0x00, }, // 'А' - { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'Б' - { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'В' - { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'Г' - { 0x00, 0x00, 0x00, 0x0c, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x1f, 0x11, 0x00, }, // 'Д' - { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Е' - { 0x00, 0x00, 0x00, 0x15, 0x15, 0x15, 0x0e, 0x15, 0x15, 0x15, 0x00, 0x00, }, // 'Ж' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x10, 0x0e, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // 'З' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x00, 0x00, }, // 'И' - { 0x00, 0x0a, 0x04, 0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x00, 0x00, }, // 'Й' - { 0x00, 0x00, 0x00, 0x19, 0x05, 0x05, 0x03, 0x05, 0x09, 0x11, 0x00, 0x00, }, // 'К' - { 0x00, 0x00, 0x00, 0x1e, 0x12, 0x12, 0x12, 0x12, 0x12, 0x11, 0x00, 0x00, }, // 'Л' - { 0x00, 0x00, 0x00, 0x11, 0x1b, 0x15, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'М' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Н' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'О' - { 0x00, 0x00, 0x00, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'П' - { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'Р' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'С' - { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Т' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, 0x00, 0x00, }, // 'У' - { 0x00, 0x00, 0x00, 0x04, 0x0e, 0x15, 0x15, 0x15, 0x0e, 0x04, 0x00, 0x00, }, // 'Ф' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x11, 0x00, 0x00, }, // 'Х' - { 0x00, 0x00, 0x00, 0x00, 0x09, 0x09, 0x09, 0x09, 0x09, 0x1f, 0x10, 0x00, }, // 'Ц' - { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x10, 0x10, 0x00, 0x00, }, // 'Ч' - { 0x00, 0x00, 0x00, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x1f, 0x00, 0x00, }, // 'Ш' - { 0x00, 0x00, 0x00, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x1f, 0x10, 0x00, }, // 'Щ' - { 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x0e, 0x12, 0x12, 0x0e, 0x00, 0x00, }, // 'Ъ' - { 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x13, 0x15, 0x15, 0x13, 0x00, 0x00, }, // 'Ы' - { 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'Ь' - { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x10, 0x1c, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // 'Э' - { 0x00, 0x00, 0x00, 0x09, 0x15, 0x15, 0x17, 0x15, 0x15, 0x09, 0x00, 0x00, }, // 'Ю' - { 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x00, 0x00, }, // 'Я' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x10, 0x1e, 0x11, 0x1e, 0x00, 0x00, }, // 'а' - { 0x00, 0x00, 0x00, 0x1e, 0x01, 0x0d, 0x13, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'б' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x11, 0x0f, 0x11, 0x0f, 0x00, 0x00, }, // 'в' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'г' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0a, 0x0a, 0x0a, 0x1f, 0x11, 0x00, }, // 'д' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'е' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x0e, 0x04, 0x0e, 0x15, 0x00, 0x00, }, // 'ж' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x09, 0x04, 0x09, 0x06, 0x00, 0x00, }, // 'з' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x19, 0x15, 0x13, 0x11, 0x00, 0x00, }, // 'и' - { 0x00, 0x00, 0x00, 0x0a, 0x04, 0x11, 0x19, 0x15, 0x13, 0x11, 0x00, 0x00, }, // 'й' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x09, 0x07, 0x09, 0x11, 0x00, 0x00, }, // 'к' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x12, 0x12, 0x12, 0x11, 0x00, 0x00, }, // 'л' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x1b, 0x15, 0x11, 0x11, 0x00, 0x00, }, // 'м' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x00, 0x00, }, // 'н' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'о' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'п' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x13, 0x11, 0x11, 0x0f, 0x01, 0x01, }, // 'р' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'с' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'т' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'у' - { 0x00, 0x00, 0x00, 0x04, 0x04, 0x0e, 0x15, 0x15, 0x15, 0x0e, 0x04, 0x04, }, // 'ф' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x00, 0x00, }, // 'х' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x09, 0x09, 0x09, 0x1f, 0x10, 0x00, }, // 'ц' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x00, 0x00, }, // 'ч' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x15, 0x15, 0x15, 0x1f, 0x00, 0x00, }, // 'ш' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x15, 0x15, 0x15, 0x1f, 0x10, 0x00, }, // 'щ' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x0e, 0x12, 0x0e, 0x00, 0x00, }, // 'ъ' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x13, 0x15, 0x13, 0x00, 0x00, }, // 'ы' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x0f, 0x11, 0x0f, 0x00, 0x00, }, // 'ь' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x1c, 0x11, 0x0e, 0x00, 0x00, }, // 'э' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x15, 0x17, 0x15, 0x09, 0x00, 0x00, }, // 'ю' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x1e, 0x11, 0x00, 0x00, }, // 'я' - { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ё' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '—' - { 0x00, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '’' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x15, 0x00, 0x00, }, // '…' - { 0x00, 0x00, 0x00, 0x0c, 0x12, 0x07, 0x02, 0x07, 0x12, 0x0c, 0x00, 0x00, }, // '€' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x1e, 0x1f, 0x1e, 0x04, 0x00, 0x00, }, // '←' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0e, 0x1f, 0x0e, 0x0e, 0x00, 0x00, }, // '↑' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0f, 0x1f, 0x0f, 0x04, 0x00, 0x00, }, // '→' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x0e, 0x1f, 0x0e, 0x04, 0x00, 0x00, }, // '↓' - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // ' ' -}; - -u32 monogram_ascii_to_idx[127] = { - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 0xFFFF, - 389, // 0x20 ' ' - 10, // 0x21 '!' - 11, // 0x22 '"' - 12, // 0x23 '#' - 13, // 0x24 '$' - 14, // 0x25 '%' - 15, // 0x26 '&' - 16, // 0x27 ''' - 17, // 0x28 '(' - 18, // 0x29 ')' - 19, // 0x2a '*' - 20, // 0x2b '+' - 21, // 0x2c ',' - 22, // 0x2d '-' - 23, // 0x2e '.' - 24, // 0x2f '/' - 0, // 0x30 '0' - 1, // 0x31 '1' - 2, // 0x32 '2' - 3, // 0x33 '3' - 4, // 0x34 '4' - 5, // 0x35 '5' - 6, // 0x36 '6' - 7, // 0x37 '7' - 8, // 0x38 '8' - 9, // 0x39 '9' - 25, // 0x3a ':' - 26, // 0x3b ';' - 27, // 0x3c '<' - 28, // 0x3d '=' - 29, // 0x3e '>' - 30, // 0x3f '?' - 31, // 0x40 '@' - 32, // 0x41 'A' - 33, // 0x42 'B' - 34, // 0x43 'C' - 35, // 0x44 'D' - 36, // 0x45 'E' - 37, // 0x46 'F' - 38, // 0x47 'G' - 39, // 0x48 'H' - 40, // 0x49 'I' - 41, // 0x4a 'J' - 42, // 0x4b 'K' - 43, // 0x4c 'L' - 44, // 0x4d 'M' - 45, // 0x4e 'N' - 46, // 0x4f 'O' - 47, // 0x50 'P' - 48, // 0x51 'Q' - 49, // 0x52 'R' - 50, // 0x53 'S' - 51, // 0x54 'T' - 52, // 0x55 'U' - 53, // 0x56 'V' - 54, // 0x57 'W' - 55, // 0x58 'X' - 56, // 0x59 'Y' - 57, // 0x5a 'Z' - 58, // 0x5b '[' - 59, // 0x5c '\' - 60, // 0x5d ']' - 61, // 0x5e '^' - 62, // 0x5f '_' - 63, // 0x60 '`' - 64, // 0x61 'a' - 65, // 0x62 'b' - 66, // 0x63 'c' - 67, // 0x64 'd' - 68, // 0x65 'e' - 69, // 0x66 'f' - 70, // 0x67 'g' - 71, // 0x68 'h' - 72, // 0x69 'i' - 73, // 0x6a 'j' - 74, // 0x6b 'k' - 75, // 0x6c 'l' - 76, // 0x6d 'm' - 77, // 0x6e 'n' - 78, // 0x6f 'o' - 79, // 0x70 'p' - 80, // 0x71 'q' - 81, // 0x72 'r' - 82, // 0x73 's' - 83, // 0x74 't' - 84, // 0x75 'u' - 85, // 0x76 'v' - 86, // 0x77 'w' - 87, // 0x78 'x' - 88, // 0x79 'y' - 89, // 0x7a 'z' - 90, // 0x7b '{' - 91, // 0x7c '|' - 92, // 0x7d '}' - 93, // 0x7e '~' -}; - -void console_redraw_commit() -{ - console_redraw_line(line_idx); - - for (u32 row = 0; row < NUM_ROWS; ++row) - { - for (int col = 0; col < NUM_COLS; ++col) - { - char ch = text[row][col]; - - if (ch == 0) continue; - - console_draw_char(ch, col, row); - } - } - - u64 t = time_current_ms(); - bool cursor_on = (t / 400) % 2; - - if (cursor_on) - { - console_draw_char('_', col_idx, line_idx); - } - - window_draw_frame(0, frame_buffer); -} - -size_t console_get_line_pos(size_t line_num) -{ - return margin + char_height*line_num; -} - -void console_redraw_line(size_t start_y) -{ - size_t start = console_get_line_pos(start_y); - size_t end = console_get_line_pos(start_y + 1) + FONT_MONOGRAM_HEIGHT; - - for(u64 y = start; y < end; ++y) - memset32(((u32*)frame_buffer) + FRAME_WIDTH * y, blue, console_width); -} - -void console_redraw_all_text() -{ - for(size_t y = 0; y < FRAME_HEIGHT; ++y) - memset32(((u32*)frame_buffer) + FRAME_WIDTH * y, blue, console_width); -} - -void console_make_space() -{ - for(size_t cur=0; cur < (NUM_ROWS-1); ++cur) - memcpy(text[cur], text[cur+1], NUM_COLS); - memset(text[NUM_ROWS-1], 0, NUM_COLS); - col_idx=0; - console_redraw_all_text(); -} - -void console_newline() -{ - if (line_idx + 1 < NUM_ROWS) - { - console_redraw_line(line_idx); - line_idx = line_idx + 1; - col_idx = 0; - } - else console_make_space(); -} - -void console_putchar(char ch) -{ - text[line_idx][col_idx] = ch; - - if (col_idx + 1 < NUM_COLS) - { - col_idx = col_idx + 1; - } - else if (line_idx + 1 < NUM_ROWS) - { - size_t old_col_idx = col_idx; - console_newline(); - min_col_idx = 0; - if(old_col_idx >= NUM_COLS) - col_idx = col_idx + 1; - } - else - { - console_make_space(); - if(min_line_idx > 0) - min_line_idx = min_line_idx - 1; - } -} - -void console_puts(u8* ch) -{ - while((*ch) != 0) - { - console_putchar(*ch); - ++ch; - } -} - -char console_input_buff[20]; - -void console_print_i64(i64 n) -{ - memset(console_input_buff, 0, sizeof(console_input_buff)); - u8 buff_start = 18; - u8 is_neg = 0; - - if(0 > n) - { - is_neg = 1; - n = -n; - } - else if (n == 0) - { - console_input_buff[buff_start] = 48; - --buff_start; - } - - for(; n!=0; --buff_start) - { - console_input_buff[buff_start] = (n % 10) + 48; - n = n / 10; - } - - if (is_neg) - { - DEBUG("setting neg sign\n"); - console_input_buff[buff_start] = '-'; - --buff_start; - } - - console_puts(console_input_buff+(buff_start+1)); -} - -void console_draw_char(char ch, size_t row_num, size_t col_num) -{ - row_num = margin + char_width * row_num; - col_num = console_get_line_pos(col_num); - u8 scale = 3; - u32 char_idx = monogram_ascii_to_idx[ch]; - - u32* d = get_point_ptr((u32*)frame_buffer, FRAME_WIDTH, row_num, col_num); - - for (u64 y = 0; y < FONT_MONOGRAM_HEIGHT; ++y) - { - u8 pixel_bits = font_monogram_data[char_idx][y]; - for (u8 i = 0; i < scale; ++i) - { - u64 x = 0; - u8 pb = pixel_bits; - - while (pb) - { - memset32(d + x, blue, scale); - if (pb & 1) memset32(d + x, 0xFFFFFF, scale); - x = x + scale; - pb = pb >> 1; - } - - d = d + FRAME_WIDTH; - } - } -} - - -//=========================================================================== -/* CANVAS */ - -size_t canvas_width; -size_t canvas_height; -#define CANVAS_PLOT_POINT_SIZE 5 - -u8 canvas_plot(u64 x, u64 y, u64 color) -{ - TRY(canvas_coord_gaurd(x, y)); - u32* p = get_point_ptr((u32*)frame_buffer, FRAME_WIDTH, x, y); - - for(u32 y = 0; y < CANVAS_PLOT_POINT_SIZE; ++y) - { - memset32(p + console_width, color, CANVAS_PLOT_POINT_SIZE); - p = p + FRAME_WIDTH; - } - return 0; -} - -u8 canvas_coord_gaurd(u32 x, u32 y) -{ - if((x + console_width) >= FRAME_WIDTH) - { - console_error("The given X coordinate exceeds the size of the canvas width"); - return 1; - } - if(y >= FRAME_HEIGHT) - { - console_error("The given y coordinate exceeds the size of the canvas height"); - return 1; - } - - return 0; -} - -void canvas_fill(u32 color) -{ - memset32(frame_buffer, color, sizeof(frame_buffer) / sizeof(u32)); -} - -//=========================================================================== -/* INTERPRETER */ - -u64 vm_status = VM_STATUS_DONE; - -char vm_command_text_buffer[1024]; -size_t vm_command_text_buffer_cursor =0; -size_t vm_command_text_buffer_read = 0; - -void vm_command_text_buffer_clear() -{ - memset(vm_command_text_buffer, 0, sizeof(vm_command_text_buffer)); - vm_command_text_buffer_read = 0; - vm_command_text_buffer_cursor = 0; -} - -void vm_command_text_buffer_backspace() -{ - if(vm_command_text_buffer_cursor > 0) - { - vm_command_text_buffer[vm_command_text_buffer_cursor] = 0; - --vm_command_text_buffer_cursor; - } -} - -#define OP_PUSH 0 -// pops the top of the stack, assigns the value to the var -#define OP_SET_VAR 1 -// pushes a var value to the stack -#define OP_GET_VAR 2 - -#define OP_JUMP 3 // jumps to instructions within a command -#define OP_JUMP_IF 4 -#define OP_JUMP_IF_NOT 5 - -// binary arth ops pop 2 vals from the stack, push the result to the stack -#define OP_ADD 6 -#define OP_SUB 7 -#define OP_MULT 8 -#define OP_DIV 9 - -#define OP_GT 10 -#define OP_LT 11 -#define OP_GT_EQ 12 -#define OP_LT_EQ 13 - -#define OP_EQ 14 -#define OP_NOT_EQ 15 - -#define OP_PRINT_INT 16 - -// Jumps to a command, as opposed to jumping to instructions as OP_JUMP does -#define OP_GOTO 17 - -// draws a pixel at x, y -#define OP_PLOT 18 -#define OP_LINE 19 -#define OP_FILL 20 -#define OP_HELP_ALL 21 - -#define OP_HALT 22 // stops execution - -#define OP_EXIT 23 // Exits the program - -#define OP_SLEEP 24 -#define OP_RAND 25 -#define OP_MOD 26 -#define OP_PRINT_STR 27 - -char* error_prefix = "Error: "; - -// Maps symbols to var positions -size_t vm_intern_capacity = 1024; -u64** vm_intern_buffer; - -// Linkedlist containing sorterd commands -// Each command is a list of instructions -// represented this way in order to insert new commands in between old ones -// the number of the command. e.g. 10 LET x 10 -> command[VM_COMMANDS_NUM] = 10 -#define VM_COMMANDS_NUM 0 -// The number of instructions that the command can hold -#define VM_COMMANDS_CAPACITY 1 -// The number of instructions that the command currently hold -#define VM_COMMANDS_CUR 2 -// command[VM_COMMANDS_NEXT] holds the address of the next command -#define VM_COMMANDS_NEXT 3 -// command[VM_COMMANDS_INSTS_BUFFER] is the buffer that actually holds the instructions -#define VM_COMMANDS_INSTS_BUFFER 4 - -u64** vm_commands_selected = NULL; -u64** vm_commands_root = NULL; - -// Vector containing variable values -size_t vm_vars_allocated = 0; -i64 vm_vars[32768]; - -// Stack that can be pushed to and poped from -size_t vm_stack_cursor = 0; -i64 vm_stack[1024]; - -u64** vm_commands_alloc(u64 num) -{ - size_t capacity = 256; - size_t meta_data_size = sizeof(u64*)*5; - size_t inst_buffer_size = sizeof(u64)*capacity; - - u64** command_meta = (u64**)malloc(meta_data_size); - NULLGAURD(command_meta); - memset(command_meta, 0, meta_data_size); - u64* inst_buffer = (u64*) malloc(inst_buffer_size); - NULLGAURD(inst_buffer); - memset(inst_buffer, 0, inst_buffer_size); - command_meta[VM_COMMANDS_CAPACITY] = (u64*)capacity; - command_meta[VM_COMMANDS_NUM] = (u64*)num; - command_meta[VM_COMMANDS_INSTS_BUFFER] = inst_buffer; - return command_meta; -} - -u64** vm_command_find(u64 num) -{ - for(u64** cur_cmd = vm_commands_root; cur_cmd != 0; cur_cmd = (u64**)cur_cmd[VM_COMMANDS_NEXT]) - { - u64 cur_cmd_num = (u64)cur_cmd[VM_COMMANDS_NUM]; - if (cur_cmd_num == num) return cur_cmd; - } - return 0; -} - -void vm_command_free(u64** cmd) -{ - free((void*)cmd[VM_COMMANDS_INSTS_BUFFER]); - free((void*) cmd); -} - -u64** vm_command_create(u64 num) -{ - - u64** new_cmd = vm_commands_alloc(num); - NULLGAURD(new_cmd); - - if(vm_commands_root == NULL) - { // initialize the root - DEBUG("Init root\n"); - new_cmd[VM_COMMANDS_NEXT] = (u64*)vm_commands_root; - vm_commands_root = new_cmd; - return new_cmd; - } - - DEBUG("Finding command\n"); - u64** prev_cmd = 0; - for(u64** cur_cmd = vm_commands_root;; cur_cmd = (u64**)cur_cmd[VM_COMMANDS_NEXT]) - { - if(cur_cmd == NULL) - { - DEBUG("Inserting command at the end"); - prev_cmd[VM_COMMANDS_NEXT] = (u64*)new_cmd; - break; - } - u64 cur_cmd_num = (u64)cur_cmd[VM_COMMANDS_NUM]; - if(cur_cmd_num > num) - { // insert command before - DEBUG("Inserting commands in between commands"); - if(cur_cmd == vm_commands_root) vm_commands_root = new_cmd; - prev_cmd[VM_COMMANDS_NEXT] = (u64*)new_cmd; - new_cmd[VM_COMMANDS_NEXT] = (u64*)cur_cmd; - break; - } - else if(cur_cmd_num == num) - { // insert command exactly at - DEBUG("Replacing existing command"); - if(cur_cmd == vm_commands_root) vm_commands_root = new_cmd; - prev_cmd[VM_COMMANDS_NEXT] = (u64*)new_cmd; - new_cmd[VM_COMMANDS_NEXT] = (u64*)cur_cmd[VM_COMMANDS_NEXT]; - vm_command_free(cur_cmd); - break; - } - prev_cmd = cur_cmd; - } - - return new_cmd; -} - -u64 vm_alloc_var() -{ - u64 allocated = vm_vars_allocated; - ++vm_vars_allocated; - return allocated; -} - -// When a symbol is interned a small area of memory that holds some meta data about the symbol is allocated -// stores the actual string that represents the symbol -#define SYM_META_STR 0 -// stores the index of the variable in vm_vars. vm_vars[sym_meta[SYM_META_LOC]] = the value of the symbol as set by LET -#define SYM_META_LOC 1 - -u64* vm_alloc_sym_meta() -{ - size_t size = sizeof(u64)*3; - u64* sym_meta = (u64*)malloc(size); - NULLGAURD(sym_meta); - memset(sym_meta, 0, size); - return sym_meta; -} - -u8 vm_intern_buffer_alloc() -{ - DEBUGS("Allocating intern buffer with size: "); - DEBUGI(vm_intern_capacity); - DEBUG(""); - - size_t interned_symbols_size = sizeof(u64**) * vm_intern_capacity; - vm_intern_buffer = (u64**)(malloc(interned_symbols_size)); - if(vm_intern_buffer == NULL) return 1; - memset(vm_intern_buffer, 0, interned_symbols_size); - return 0; -} - -void resize_interned_sym_buffer() -{ - size_t old_capacity = vm_intern_capacity; - u64** old_buffer = vm_intern_buffer; - - vm_intern_capacity = vm_intern_capacity * 2; - vm_intern_buffer_alloc(); - - DEBUG("Copying interned symbols from old to new buffer"); - for(size_t cursor = 0; cursor < old_capacity; ++cursor) - { - u64* sym_meta = old_buffer[cursor]; - char* str = sym_meta[SYM_META_STR]; - u32 str_hash = hash(str); - u64 idx = (vm_intern_capacity - 1) & str_hash; - - DEBUG("Finding a spot for symbol: "); - DEBUGS(str); - DEBUG(""); - - while(vm_intern_buffer[idx] != 0) - { - DEBUGS("Couldn't find a spot at index: "); - DEBUGI(idx); - DEBUG(""); - ++idx; - if(idx >= vm_intern_buffer) idx = 0; - } - DEBUG("Found a spot at index: "); - DEBUGI(idx); - DEBUG(""); - - vm_intern_buffer[idx] = sym_meta; - } -} - -u64* vm_intern(u8* sym, u64 len, u32 hash) -{ - u64 idx = (vm_intern_capacity - 1) & hash; - DEBUG("Interning a new sym: "); - DEBUGS(sym); - for(size_t cursor = idx;; ++cursor) - { - if (cursor >= vm_intern_capacity) - { - if(idx == 0) break; - cursor = 0; - } - - DEBUGS("Attempting to locate symbol at index: "); - DEBUGI(cursor); - DEBUG(""); - - u64* sym_meta = vm_intern_buffer[cursor]; - if (sym_meta == NULL) - { - DEBUG("Symbol not found, creating a new entry"); - char* sym_str; - sym_str = (char*)malloc(len+1); - NULLGAURD(sym_str); - memcpy(sym_str, sym, len); - sym_str[len] = 0; - - sym_meta = vm_alloc_sym_meta(); - NULLGAURD(sym_meta); - sym_meta[SYM_META_STR] = (u64)sym_str; - - vm_intern_buffer[cursor] = (u64)sym_meta; - return sym_meta; - } - else if (strncmp(sym_meta[SYM_META_STR], sym, len) == 0) - { - DEBUG("Symbol have been found"); - return sym_meta; - } - else if(cursor == (idx-1)) - { - break; - } - } - - DEBUG("Not enough space in intern buffer"); - resize_interned_sym_buffer(); - DEBUG("Done resizing intern buffer"); - return vm_intern(sym, len, hash); -} - -u8 vm_symbol_is_initialized(u64* sym) -{ - return sym[SYM_META_LOC] != 0; -} - -u64 vm_symbol_init_or_get_var(u64* sym) -{ - if(!vm_symbol_is_initialized(sym)) - { - sym[SYM_META_LOC] = ++vm_vars_allocated; - } - return sym[SYM_META_LOC]-1; -} - -void vm_symbol_init_val(u64* sym, u64 val) -{ - u64 var = vm_symbol_init_or_get_var(sym); - vm_vars[var] = val; -} - -// translates symbol strings to symbols -i64 vm_symbol_get_var(u64* sym) -{ - if(!vm_symbol_is_initialized(sym)) - { - console_newline(); - console_puts(error_prefix); - console_puts("Trying to access "); - console_newline(); - console_puts("an uninitialized variable"); - console_puts(sym[SYM_META_STR]); - return -1; - } - return sym[SYM_META_LOC]-1; -} - -u32 hash(u8 *str) -{ - u32 hash = 5381; - u8 c; - - while (c = *str) - { - hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ - ++str; - } - - return hash; -} - -u64* vm_commands_sym_else; -u64* vm_commands_sym_let; -u64* vm_commands_sym_goto; -u64* vm_commands_sym_run; -u64* vm_commands_sym_if; -u64* vm_commands_sym_plot; -u64* vm_commands_sym_line; -u64* vm_commands_sym_clear; -u64* vm_commands_sym_help; -u64* vm_commands_sym_halt; -u64* vm_commands_sym_exit; -u64* vm_commands_sym_quit; -u64* vm_commands_sym_fill; -u64* vm_commands_sym_sleep; -u64* vm_commands_sym_rand; -u64* vm_commands_sym_print; - -u64* vm_init_sym(char* sym) -{ - return vm_intern(sym, strlen(sym), hash(sym)); -} - -int vm_init() -{ - TRY(vm_intern_buffer_alloc()); - - vm_commands_sym_else = vm_init_sym("ELSE"); - vm_commands_sym_let = vm_init_sym("LET"); - vm_commands_sym_goto = vm_init_sym("GOTO"); - vm_commands_sym_run = vm_init_sym("RUN"); - vm_commands_sym_if = vm_init_sym("IF"); - vm_commands_sym_plot = vm_init_sym("PLOT"); - vm_commands_sym_line = vm_init_sym("LINE"); - vm_commands_sym_clear = vm_init_sym("CLEAR"); - vm_commands_sym_help = vm_init_sym("HELP"); - vm_commands_sym_halt = vm_init_sym("HALT"); - vm_commands_sym_exit = vm_init_sym("EXIT"); - vm_commands_sym_quit = vm_init_sym("QUIT"); - vm_commands_sym_fill = vm_init_sym("FILL"); - vm_commands_sym_sleep = vm_init_sym("SLEEP"); - vm_commands_sym_rand = vm_init_sym("RAND"); - vm_commands_sym_print = vm_init_sym("PRINT"); - - vm_symbol_init_val(vm_init_sym("BLUE"), blue); - vm_symbol_init_val(vm_init_sym("RED"), red); - vm_symbol_init_val(vm_init_sym("GREEN"), green); - vm_symbol_init_val(vm_init_sym("BLACK"), black); - vm_symbol_init_val(vm_init_sym("WHITE"), white); - - canvas_width = FRAME_WIDTH - console_width-CANVAS_PLOT_POINT_SIZE; - canvas_height = FRAME_HEIGHT-CANVAS_PLOT_POINT_SIZE; - vm_symbol_init_val(vm_init_sym("CWIDTH"), canvas_width); - vm_symbol_init_val(vm_init_sym("CHEIGHT"), canvas_height); - - return 0; -} - -void vm_command_double_size() -{ - u64* old_buff = vm_commands_selected[VM_COMMANDS_INSTS_BUFFER]; - size_t old_capacity = (size_t)vm_commands_selected[VM_COMMANDS_CAPACITY]; - size_t new_capacity = old_capacity*2; - size_t old_size = old_capacity*sizeof(u64); - size_t new_size = new_capacity*sizeof(u64); - - u64* new_buff = (u64*)malloc(new_size); - if(new_buff == NULL) - { - DEBUG("Failed to allocate more memory for the selected command"); - exit(1); - } - - memset(new_buff, 0, new_size); - memcpy(new_buff, old_buff, old_size); - vm_commands_selected[VM_COMMANDS_INSTS_BUFFER] = new_buff; - vm_commands_selected[VM_COMMANDS_CAPACITY] = new_capacity; - free((void*)old_buff); -} - -u64 vm_bytecode_pointer_inc() -{ - u64 current_val = (u64)vm_commands_selected[VM_COMMANDS_CUR]; - vm_commands_selected[VM_COMMANDS_CUR] = (u64*)(current_val + 1); - if(vm_commands_selected[VM_COMMANDS_CUR] >= vm_commands_selected[VM_COMMANDS_CAPACITY]) - { - DEBUG("Exceeded command capacity"); - vm_command_double_size(); - } - return current_val; -} - -u64 vm_bytecode_get(u8 op, u64 arg) -{ - return ((u64)(op & 255) << 56) | (arg & (((u64)1<<56) - 1)); -} - -void vm_bytecode_patch(u64 inst_num, u8 op, u64 arg) -{ - vm_commands_selected[VM_COMMANDS_INSTS_BUFFER][inst_num] = vm_bytecode_get(op, arg); -} - -void vm_bytecode_emit(u8 op, u64 arg) -{ - DEBUGS("VM command capacity"); - DEBUGI(vm_commands_selected[VM_COMMANDS_CAPACITY]); - DEBUG(""); - u64 pos = (u64)vm_commands_selected[VM_COMMANDS_CUR]; - vm_commands_selected[VM_COMMANDS_INSTS_BUFFER][pos] = vm_bytecode_get(op, arg); - vm_bytecode_pointer_inc(); -} - -void ignore_ws() -{ - while(vm_command_text_buffer_cursor > vm_command_text_buffer_read && - vm_command_text_buffer[vm_command_text_buffer_read] == 32) - ++vm_command_text_buffer_read; -} - -u8 peek_ch(u8 n) -{ - ignore_ws(); - if(vm_command_text_buffer_read >= vm_command_text_buffer_cursor) return 0; - return (u8)vm_command_text_buffer[vm_command_text_buffer_read + n]; -} - -u8 peek_next() -{ - return peek_ch(0); -} - -u8 read_ch() -{ - ignore_ws(); - u8 ret = (u8)vm_command_text_buffer[vm_command_text_buffer_read]; - ++vm_command_text_buffer_read; - return ret; -} - -u8 did_read_all_input() -{ - return vm_command_text_buffer_read >= vm_command_text_buffer_cursor; -} - -i64 read_uint() -{ - ignore_ws(); - size_t start = vm_command_text_buffer_read; - u64 acc = 0; - while(!did_read_all_input()) - { - - u8 cur_char = (u8)vm_command_text_buffer[vm_command_text_buffer_read]; - - cur_char = cur_char - 48; - if(cur_char > 9) - { - break; - } - - acc = acc * 10 + cur_char; - ++vm_command_text_buffer_read; - } - - if(start == vm_command_text_buffer_read) return -1; - return acc; -} - -// TODO combine read_string and read_sym to some common func -u64* read_string() -{ - ignore_ws(); - u8 bracket = vm_command_text_buffer[vm_command_text_buffer_read]; - if(bracket != '"') return NULL; - - size_t str_start = ++vm_command_text_buffer_read; - - u32 hash = 5381; - while(!did_read_all_input()) - { - u8 cur_char = (u8)vm_command_text_buffer[vm_command_text_buffer_read]; - if (cur_char == '\\') - { - ++vm_command_text_buffer_read; - } - else if (cur_char == '"') break; - hash = ((hash << 5) + hash) + cur_char; - - ++vm_command_text_buffer_read; - } - if(vm_command_text_buffer_read == str_start) return NULL; - - return vm_intern((u8*) (vm_command_text_buffer+str_start), vm_command_text_buffer_read-str_start, hash); -} - -u64* read_sym() -{ - DEBUG("Reading sym"); - ignore_ws(); - size_t sym_start = vm_command_text_buffer_read; - u32 hash = 5381; - while(!did_read_all_input()) - { - u8 cur_char = (u8)vm_command_text_buffer[vm_command_text_buffer_read]; - // TODO accept symbols with numbers, _, etc - if(!((90 >= cur_char && cur_char >= 65) || (122 >= cur_char && cur_char >= 97))) break; - hash = ((hash << 5) + hash) + cur_char; - ++vm_command_text_buffer_read; - } - if(vm_command_text_buffer_read == sym_start) return NULL; - return vm_intern((u8*) (vm_command_text_buffer+sym_start), vm_command_text_buffer_read-sym_start, hash); -} - -void console_error(char* err) -{ - console_newline(); - console_puts(error_prefix); - console_puts(err); - console_newline(); -} - -u8 vm_emit_args(u8 expected_args_count) -{ - char current = read_ch(); - if(current != '(') - { - console_error("Expected to find an ( but did not"); - return 1; - } - current = peek_next(); - u8 actual_args_count = 0; - while(current != ')') - { - if(current == 0) - { - console_error("Expected function call to end with )"); - return 1; - } - else if (actual_args_count > expected_args_count) - { - console_error("Function got too many arguments"); - return 1; - } - ++actual_args_count; - vm_emit_exp(); - } - read_ch(); - return 0; -} - -u64 vm_emit_prim() -{ - i64 num = read_uint(); - if(num >= 0) - { - vm_bytecode_emit(OP_PUSH, num); - return 0; - } - - u64* sym = read_sym(); - char next_ch = peek_next(); - - if(sym != 0) - { - if(next_ch == '(') - { - if (sym == vm_commands_sym_rand) - { - vm_emit_args(0); - vm_bytecode_emit(OP_RAND, 0); - } - else - { - console_error("Undefined function"); - return 1; - } - return 0; - } - - i64 var = vm_symbol_get_var(sym); - if(0 > var) return 1; - vm_bytecode_emit(OP_GET_VAR, var); - return 0; - } - - if(next_ch == '(') - { - read_ch(); - vm_emit_exp(); - next_ch = read_ch(); - if(next_ch != ')') - { - console_error("Expected to find ) but did not"); - return 1; - } - return 0; - } - - if (next_ch == ')') return 0; - - console_error("Unexpected token in expression"); - return 1; -} - -u64 vm_emit_factor() -{ - u8 next_ch; - u8 op; - - TRY(vm_emit_prim()); - while(1) - { - next_ch = peek_next(); - if('*' == next_ch) op = OP_MULT; - else if('/' == next_ch) op = OP_DIV; - else if('%' == next_ch) op = OP_MOD; - else break; - - read_ch(); - TRY(vm_emit_prim()); - vm_bytecode_emit(op, 0); - } - - return 0; -} - -u64 emit_term() -{ - u8 op; - u8 next_ch; - - TRY(vm_emit_factor()); - while(1) - { - next_ch = peek_next(); - if('+' == next_ch) op = OP_ADD; - else if('-' == next_ch) op = OP_SUB; - else break; - - read_ch(); - TRY(vm_emit_factor()); - vm_bytecode_emit(op, 0); - } - return 0; -} - -u64 vm_emit_comparison() -{ - u8 op; - u8 next_ch; - - TRY(emit_term()); - while(1) - { - next_ch = peek_next(); - if('>' == next_ch) - { - read_ch(); - next_ch = peek_next(); - op = OP_GT; - if ('=' == next_ch) - { - op = OP_GT_EQ; - read_ch(); - } - } - else if('<' == next_ch) - { - read_ch(); - next_ch = peek_next(); - op = OP_LT; - if ('=' == next_ch) - { - op = OP_LT_EQ; - read_ch(); - } - } - else break; - - TRY(emit_term()); - vm_bytecode_emit(op, 0); - } - return 0; -} - -u64 vm_emit_exp() -{ - u8 op; - u8 next_ch; - - TRY(vm_emit_comparison()); - - while(1) - { - next_ch = peek_next(); - - if(next_ch == '!' && peek_ch(1) == '=') op = OP_NOT_EQ; - else if(next_ch == '=' && peek_ch(1) == '=') op = OP_EQ; - else break; - read_ch(); - read_ch(); - TRY(vm_emit_comparison()); - - vm_bytecode_emit(op, 0); - } - return 0; -} - -void emit_print_str(char* s) -{ - u64* interned_s = vm_intern(s, strlen(s), hash(s)); - u64 var = vm_symbol_init_or_get_var(interned_s); - vm_vars[var] = (u64)interned_s; - vm_bytecode_emit(OP_PRINT_STR, var); -} - -u8 vm_emit_cmd(u64* command) -{ - DEBUG("Emitting command"); - - if(command == vm_commands_sym_help) - { - u64* sym = read_sym(); - if(sym == NULL) - { - DEBUG("Emitting help command"); - vm_bytecode_emit(OP_HELP_ALL, 0); - } - else if (sym == vm_commands_sym_let) - emit_print_str("Assigns the value of the expression to the variable named by the symbol. LET {symbol} {expression}"); - else if (sym == vm_commands_sym_goto) - emit_print_str("Goes to a loaded command. Commands can be loaded by prefixing them with a number. GOTO {cmd number}"); - else if (sym == vm_commands_sym_run) - emit_print_str("Runs loaded commands. Commands can be loaded by prefixing them with a number"); - else if (sym == vm_commands_sym_if) - emit_print_str("IF {predicate expression} {then command} ELSE {else command}"); - else if (sym == vm_commands_sym_plot) - emit_print_str("Plots a dot at x and y. PLOT {x} {y} {color}"); - else if (sym == vm_commands_sym_line) - emit_print_str("Draws a line from (x0, y0) to (x1, y1). LINE {x0} {y0} {x1} {y1} {color}"); - else if (sym == vm_commands_sym_clear) - emit_print_str("Clears the canvas"); - else if (sym == vm_commands_sym_halt) - emit_print_str("Stops command execution. You can use it to stop loops"); - else if (sym == vm_commands_sym_exit) - emit_print_str("Exit the program"); - else if (sym == vm_commands_sym_quit) - emit_print_str("Same as exit"); - else if (sym == vm_commands_sym_fill) - emit_print_str("Fills the canvas with a color. FILL {color}"); - else if (sym == vm_commands_sym_sleep) - emit_print_str("Sleep before executing the next command. SLEEP {time in milliseconds}"); - else if (sym == vm_commands_sym_rand) - emit_print_str("A function that returns a random number. Example: LET x RAND()"); - else if (sym == vm_commands_sym_print) - emit_print_str("Prints a string or a number. PRINT \"SOME STRING\""); - else - { - console_error("Command or function is not found"); - return 1; - } - } - - else if(command == vm_commands_sym_let) - { - DEBUG("Emitting LET command"); - u64* sym = read_sym(); - if (sym == NULL) - { - console_error("LET is expected to be followed by a symbol but was not"); - return 1; - } - TRY(vm_emit_exp()); - u64 var = vm_symbol_init_or_get_var(sym); - vm_bytecode_emit(OP_SET_VAR , var); - } - else if (command == vm_commands_sym_print) - { - DEBUG("Emitting print"); - u64* str = read_string(); - if(str != NULL) - { - DEBUG("Printing string"); - u64 var = vm_symbol_init_or_get_var(str); - vm_vars[var] = (u64)str; - vm_bytecode_emit(OP_PRINT_STR, var); - } - else - { - if(vm_emit_exp()) - { - console_error("PRINT expects to be followed by a string or an expression"); - return 1; - } - DEBUG("Printing int"); - vm_bytecode_emit(OP_PRINT_INT, 0); - } - } - else if (command == vm_commands_sym_sleep) - { - TRY(vm_emit_exp()); - vm_bytecode_emit(OP_SLEEP, 0); - } - else if (command == vm_commands_sym_goto) - { - DEBUG("Emitting GOTO"); - TRY(vm_emit_exp()); - vm_bytecode_emit(OP_GOTO, 0); - } - else if (command == vm_commands_sym_if) - { - DEBUG("Emitting IF"); - TRY(vm_emit_exp()); - u64 jump_to_else_pos = vm_bytecode_pointer_inc(); // resever inst to jump to else if pred is false - command = read_sym(); - TRY(vm_emit_cmd(command)); // Then cmd - - u64 jump_to_else_end_pos = vm_bytecode_pointer_inc(); // reserve inst to jump to end of else; - - // emitting else - u64* else_sym = read_sym(); - if(else_sym != vm_commands_sym_else) - { - console_error("Expected the Then clause to be followed by ELSE"); - return 1; - } - u64 start_of_else = (u64)vm_commands_selected[VM_COMMANDS_CUR]; - command = read_sym(); - TRY(vm_emit_cmd(command)); // Then cmd - - u64 end_of_else = (u64)vm_commands_selected[VM_COMMANDS_CUR]; - - vm_bytecode_patch(jump_to_else_pos, OP_JUMP_IF_NOT, start_of_else); - vm_bytecode_patch(jump_to_else_end_pos, OP_JUMP, end_of_else); - } - else if (command == vm_commands_sym_plot) - { - DEBUG("Emitting plot"); - TRY(vm_emit_exp()); - TRY(vm_emit_exp()); - TRY(vm_emit_exp()); - vm_bytecode_emit(OP_PLOT, 0); - } - else if (command == vm_commands_sym_line) - { - DEBUG("Emitting line"); - TRY(vm_emit_exp()); - TRY(vm_emit_exp()); - TRY(vm_emit_exp()); - TRY(vm_emit_exp()); - TRY(vm_emit_exp()); - vm_bytecode_emit(OP_LINE, 0); - } - else if (command == vm_commands_sym_halt) - { - DEBUG("Emitting clear"); - vm_bytecode_emit(OP_HALT, 0); - } - else if (command == vm_commands_sym_clear) - { - DEBUG("Emitting clear"); - vm_bytecode_emit(OP_PUSH, white); - vm_bytecode_emit(OP_FILL, 0); - } - else if (command == vm_commands_sym_exit || command == vm_commands_sym_quit) - { - DEBUG("emitting exit command"); - vm_bytecode_emit(OP_EXIT, 0); - } - else if (command == vm_commands_sym_fill) - { - DEBUG("emitting exit command"); - TRY(vm_emit_exp()); // the color to fill the screen with - vm_bytecode_emit(OP_FILL, 0); - } - else if (did_read_all_input()) - { - DEBUG("Printing symbol value"); - if(command == NULL) return 1; - i64 var = vm_symbol_get_var(command); - if(0 > var) return 1; - console_newline(); - console_print_i64(vm_vars[var]); - } - else - { - console_error("Unrecognized command"); - return 1; - } - - return 0; -} - -void vm_load_cmd() -{ - vm_commands_selected = NULL; - if(vm_command_text_buffer_cursor == 0) return; - - DEBUGS(vm_command_text_buffer); - - i64 cmd_num = read_uint(); - u64* command = read_sym(); - if (command == vm_commands_sym_run) - { - vm_commands_selected = vm_commands_root; - return; - } - DEBUG("cmd_num\n"); - DEBUGI(cmd_num); - DEBUG(""); - u64** cmd; - if(cmd_num < 0) - { - DEBUG("Creating disposable cmd"); - cmd = vm_commands_alloc(cmd_num); - } - else - { - DEBUG("Retrieving cmd"); - cmd = vm_command_create(cmd_num); - } - - if(cmd == NULL) - { - DEBUG("Failed to allocate command"); - return; - } - - vm_commands_selected = cmd; - - if(command == NULL) command = read_sym(); - - if(vm_emit_cmd(command)) - { - if(cmd_num >= 0) vm_command_create(cmd_num); - else vm_command_free(cmd); - vm_commands_selected = NULL; - } - else if (cmd_num >= 0) - { - vm_commands_selected = NULL; - } - vm_command_text_buffer_clear(); -} - -i64 vm_pop() -{ - i64 val = vm_stack[--vm_stack_cursor]; - vm_stack[vm_stack_cursor] = 0; - return val; -} - -void vm_push(i64 v) -{ - vm_stack[vm_stack_cursor] = v; - ++vm_stack_cursor; -} - -#define BIN_OP(opcode, c_op) \ - else if (op == opcode) \ -{ \ - val2 = vm_pop(); \ - val1 = vm_pop(); \ - DEBUGS("OP: "); \ - DEBUGI(op); \ - DEBUGS("\n"); \ - DEBUGS("val1: "); \ - DEBUGI(val1); \ - DEBUGS("\n"); \ - DEBUGS("val2: "); \ - DEBUGI(val2); \ - DEBUGS("\n"); \ - i64 result = val1 c_op val2; \ - DEBUGS("result: "); \ - DEBUGI(result); \ - DEBUGS("\n"); \ - vm_push(result); \ -} - -#define PRINT_OP(op_str, op_name) else if(op == op_name) \ -{ \ - puts(op_str); \ -} - -void print_inst(u64 inst) -{ - u8 op = (u8)(inst >> 56); - u64 arg = (inst & (((u64)1<<56) - 1)); - puts("ints:"); - print_i64(inst); - puts("\n"); - puts("op:"); - print_i64(op); - puts("\n"); - puts("name:"); - if(0){} - PRINT_OP("OP_PUSH", OP_PUSH) - PRINT_OP("OP_SET_VAR", OP_SET_VAR) - PRINT_OP("OP_GET_VAR", OP_GET_VAR) - PRINT_OP("OP_JUMP", OP_JUMP) - PRINT_OP("OP_JUMP_IF", OP_JUMP_IF) - PRINT_OP("OP_JUMP_IF_NOT", OP_JUMP_IF_NOT) - PRINT_OP("OP_ADD", OP_ADD) - PRINT_OP("OP_SUB", OP_SUB) - PRINT_OP("OP_MULT", OP_MULT) - PRINT_OP("OP_DIV", OP_DIV) - PRINT_OP("OP_GT", OP_GT) - PRINT_OP("OP_LT", OP_LT) - PRINT_OP("OP_GT_EQ", OP_GT_EQ) - PRINT_OP("OP_LT_EQ", OP_LT_EQ) - PRINT_OP("OP_EQ", OP_EQ) - PRINT_OP("OP_NOT_EQ", OP_NOT_EQ) - PRINT_OP("OP_PRINT_INT", OP_PRINT_INT) - PRINT_OP("OP_GOTO", OP_GOTO) - PRINT_OP("OP_HALT", OP_HALT) - PRINT_OP("OP_FILL", OP_FILL) - PRINT_OP("OP_EXIT", OP_EXIT) - - puts("\n"); - puts("arg:"); - print_i64(arg); - puts("\n"); -} - -void print_insts(u64** cmd) -{ - u64* cmd_insts = cmd[VM_COMMANDS_INSTS_BUFFER]; - u64 cmd_size = (u64)cmd[VM_COMMANDS_CUR]; - puts("INSTRUCTION PRINTOUT \n"); - for(size_t inst_idx = 0; (inst_idx < cmd_size); ++inst_idx) - { - puts("--------------\n"); - u64 inst = cmd_insts[inst_idx]; - puts("idx:"); - print_i64(inst_idx); - puts("\n"); - print_inst(inst); - } - - puts("-- END INSTRUCTION PRINTOUT"); -} - -void vm_hand_control_back() -{ - vm_commands_selected = NULL; - vm_status = VM_STATUS_DONE; - console_print_ready(); - min_line_idx = line_idx; - console_redraw_commit(); -} - -void vm_exec() -{ - if(vm_commands_selected == NULL || vm_status == VM_STATUS_HALT) - { - vm_hand_control_back(); - return; - } - - vm_status = VM_STATUS_RUNNING; - i64 val1; - i64 val2; - u64 sleep = 0; - u64** cmd = vm_commands_selected; - vm_commands_selected = (u64**) cmd[VM_COMMANDS_NEXT]; - u64 cur_cmd_num = (u64)cmd[VM_COMMANDS_NUM]; - - u64* cmd_insts = cmd[VM_COMMANDS_INSTS_BUFFER]; - u64 cmd_size = (u64)cmd[VM_COMMANDS_CUR]; - - for(size_t inst_idx = 0; (inst_idx < cmd_size);) - { - u64 inst = cmd_insts[inst_idx]; - u8 op = (u8)(inst >> 56); - u64 arg = (inst & (((u64)1<<56) - 1)); - - // TODO check for overflow - if(op == OP_PUSH) vm_push(arg); - else if (op == OP_SET_VAR) vm_vars[arg] = vm_pop(); - else if (op == OP_GET_VAR) vm_push(vm_vars[arg]); - else if (op == OP_PRINT_INT) - { - console_newline(); - console_print_i64(vm_pop()); - } - else if (op == OP_GOTO) vm_commands_selected = vm_command_find(vm_pop()); - else if (op == OP_RAND) vm_push(rand()); - else if (op == OP_PRINT_STR) - { - u64* str = (u64)vm_vars[arg]; - console_newline(); - console_puts(str[SYM_META_STR]); - break; - } - else if (op == OP_SLEEP) - { - sleep = vm_pop(); - break; - } - else if (op == OP_HALT) - { - vm_hand_control_back(); - return; - } - else if (op == OP_HELP_ALL) - { - console_newline(); - console_puts("Commands: "); - console_newline(); - console_puts("LET GOTO IF PLOT LINE"); - console_newline(); - console_puts("CLEAR FILL RUN HALT EXIT SLEEP PRINT"); - console_newline(); - console_newline(); - console_puts("Builtin functions:"); - console_newline(); - console_puts("RAND"); - console_newline(); - console_newline(); - console_puts("Builtin variables:"); - console_newline(); - console_puts("BLUE RED GREEN BLACK"); - console_newline(); - console_puts("WHITE"); - console_newline(); - console_puts("CWIDTH(canvas width)"); - console_newline(); - console_puts("CHEIGHT(canvas height)"); - console_newline(); - console_newline(); - console_puts("Canvas size: "); - console_print_i64(canvas_width); - console_puts("X"); - console_print_i64(canvas_height); - console_newline(); - console_newline(); - console_puts("Use HELP {command or function} for more information"); - } - else if (op == OP_EXIT) - { - DEBUG("Exiting the program"); - exit(0); - } - else if (op == OP_FILL) - { - DEBUG("Filling the screen color"); - u32 color = vm_pop(); - canvas_fill(color); - console_redraw_all_text(); - } - else if (op == OP_JUMP) - { - if (arg > cmd_size) break; - DEBUG("Unconditionally jumping"); - inst_idx = arg; - continue; - } - else if (op == OP_JUMP_IF_NOT) - { - DEBUG("EXECUTING OP_JUMP_IF_NOT"); - if (arg > cmd_size) break; - if(!vm_pop()) - { - DEBUG("DETECTED A NOT SO jumping"); - inst_idx = arg; - continue; - } - } - else if (op == OP_PLOT) - { - u64 color = vm_pop(); - u64 y = vm_pop(); - u64 x = vm_pop(); - canvas_plot(x, y, color); - } - else if (op == OP_LINE) - { - u32 color = (u32)vm_pop(); - u32 y1 = (u32)vm_pop(); - u32 x1 = (u32)vm_pop(); - u32 y0 = (u32)vm_pop(); - u32 x0 = (u32)vm_pop(); - if(canvas_coord_gaurd(x0, y0)) break; - if(canvas_coord_gaurd(x1, y1)) break; - draw_line((u32*)frame_buffer, FRAME_WIDTH, FRAME_HEIGHT, console_width + x0, y0, console_width + x1, y1, color); - } - BIN_OP(OP_ADD, +) - BIN_OP(OP_SUB, -) - BIN_OP(OP_MULT, *) - BIN_OP(OP_DIV, /) - BIN_OP(OP_GT, >) - BIN_OP(OP_LT, <) - BIN_OP(OP_GT_EQ, >=) - BIN_OP(OP_LT_EQ, <=) - BIN_OP(OP_EQ, ==) - BIN_OP(OP_NOT_EQ, !=) - BIN_OP(OP_MOD, %) - else - { - DEBUG("unrecognized command"); - vm_hand_control_back(); - return; - } - DEBUG("executing inst"); - ++inst_idx; - } - - time_delay_cb(sleep, vm_exec); -} From c4c9de49c240698e089b4bc52aad6ac61da80b7c Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 19:22:55 -0400 Subject: [PATCH 62/80] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 785813b..4e673ce 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Current features: - Untyped design for simplicity - Little-endian byte ordering (like x86, ARM & RISC-V) - 32-bit and 64-bit integer ops, 32-bit floating-point support -- Separate flat, linear address spaces for code and data (Harvard architecture) +- Separate flat, linear address spaces for code and data ([Harvard architecture](https://en.wikipedia.org/wiki/Harvard_architecture)) - Thread-based parallelism - Built-in, easy to use [assembler](vm/src/asm.rs) with a [simple syntax](vm/examples) - Event-driven event execution model compatible with async operations @@ -125,6 +125,8 @@ The repository is organized into a 3 different subprojects, each of which is a R - [`/ncc/examples/*`](ncc/examples): Example C source files that can be compiled by NCC - `/api`: A system to document and automatically export bindings for UVM system calls and constants. - `/api/syscalls.json`: Declarative list of system calls exposed by UVM. +- `/doc`: Markdown documentation for UVM + - [`/doc/syscalls.json`](doc/syscalls.md): List of system calls and constants accessible to UVM programs The `ncc` compiler is, at the time of this writing, incomplete in that it lacks some C features and the error messages need improvement. This compiler was implemented to serve as an example of how to write a compiler that targets UVM, and to write some library code to be used by other programs. Over From b438abc45653c1e8b0f213aae44f2e61ae060f0f Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 19:38:43 -0400 Subject: [PATCH 63/80] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e673ce..ac7ad65 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ cd vm cargo build ``` -To run an asm file with UVM: +To run an [asm file](vm/examples) with UVM: ```sh cargo run examples/fizzbuzz.asm ``` From ed629487d92d2fef962245f7ea312472450aff4b Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 3 Sep 2024 23:14:04 -0400 Subject: [PATCH 64/80] Add insns to access thread-local variables --- vm/src/vm.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 76c6593..c9a9334 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -216,6 +216,14 @@ pub enum Op load_global_u64 */ + // Set thread-local variable + // thread_set (val) + thread_set, + + // Get thread-local variable + // thread_get + thread_get, + // NOTE: may want to wait for this because it's not RISC, // but it could help reduce code flag // NOTE: should this insn have a jump offset built in? @@ -643,6 +651,9 @@ pub struct Thread // List of stack frames (activation records) frames: Vec, + + // Thread-local variables + locals: Vec, } impl Thread @@ -656,6 +667,7 @@ impl Thread heap, stack: Vec::default(), frames: Vec::default(), + locals: Vec::default(), } } @@ -1436,6 +1448,27 @@ impl Thread unsafe { *heap_ptr = val; } } + Op::thread_set => { + let idx = self.code.read_pc::(&mut pc) as usize; + let val = self.pop(); + + if idx >= self.locals.len() { + self.locals.resize(idx + 1, Value::from(0)); + } + + self.locals[idx] = val; + } + + Op::thread_get => { + let idx = self.code.read_pc::(&mut pc) as usize; + + if idx >= self.locals.len() { + self.push(Value::from(0)); + } else { + self.push(self.locals[idx]) + } + } + Op::jmp => { let offset = self.code.read_pc::(&mut pc) as isize; pc = ((pc as isize) + offset) as usize; @@ -1784,7 +1817,7 @@ mod tests // Keep track of how many short opcodes we have so far dbg!(Op::exit as usize); - assert!(Op::exit as usize <= 113); + assert!(Op::exit as usize <= 115); } #[test] From 32a6d8f4fdfcae381ca727b50178814a02c5e107 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Wed, 4 Sep 2024 16:09:36 -0400 Subject: [PATCH 65/80] Update thread_spawn to take an argument value --- api/syscalls.json | 6 +++++- doc/syscalls.md | 4 ++-- ncc/include/uvm/syscalls.h | 6 +++--- ncc/tests/threads.c | 8 ++++---- vm/src/constants.rs | 2 +- vm/src/host.rs | 6 +++--- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/api/syscalls.json b/api/syscalls.json index 4a44cca..3ccdf2d 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -132,6 +132,10 @@ [ "void*", "fptr" + ], + [ + "void*", + "arg" ] ], "returns": [ @@ -140,7 +144,7 @@ ], "permission": "default_allowed", "const_idx": 29, - "description": "Spawn a new thread running the given function." + "description": "Spawn a new thread running the given function with the argument value `arg`." }, { "name": "thread_id", diff --git a/doc/syscalls.md b/doc/syscalls.md index 3c3f783..8c39931 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -71,12 +71,12 @@ Grow the heap to a new size given in bytes. This is similar to the `brk()` syste ## thread_spawn ``` -u64 thread_spawn(void* fptr) +u64 thread_spawn(void* fptr, void* arg) ``` **Returns:** `u64 tid` -Spawn a new thread running the given function. +Spawn a new thread running the given function with the argument value `arg`. ## thread_id diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index 5859b71..47527bc 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -29,9 +29,9 @@ // Grow the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. If the requested size is smaller than the current heap size, this is a no-op. Returns the new heap size in bytes. #define vm_grow_heap(__num_bytes) asm (__num_bytes) -> u64 { syscall vm_grow_heap; } -// u64 thread_spawn(void* fptr) -// Spawn a new thread running the given function. -#define thread_spawn(__fptr) asm (__fptr) -> u64 { syscall thread_spawn; } +// u64 thread_spawn(void* fptr, void* arg) +// Spawn a new thread running the given function with the argument value `arg`. +#define thread_spawn(__fptr, __arg) asm (__fptr, __arg) -> u64 { syscall thread_spawn; } // u64 thread_id() // Get the id of the current thread. diff --git a/ncc/tests/threads.c b/ncc/tests/threads.c index a771774..08d0d5c 100644 --- a/ncc/tests/threads.c +++ b/ncc/tests/threads.c @@ -1,17 +1,17 @@ #include #include -u64 thread_fn() +u64 thread_fn(u64 arg) { thread_sleep(5); - return 7; + return arg + 1; } int main() { - u64 tid = thread_spawn(thread_fn); + u64 tid = thread_spawn(thread_fn, 7); u64 ret = thread_join(tid); - assert(ret == 7); + assert(ret == 8); return 0; } diff --git a/vm/src/constants.rs b/vm/src/constants.rs index 8e0e914..b4c2a0e 100644 --- a/vm/src/constants.rs +++ b/vm/src/constants.rs @@ -72,7 +72,7 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ Some(SysCallDesc { name: "putchar", const_idx: 26, argc: 1, has_ret: true }), Some(SysCallDesc { name: "memcmp", const_idx: 27, argc: 3, has_ret: true }), Some(SysCallDesc { name: "thread_id", const_idx: 28, argc: 0, has_ret: true }), - Some(SysCallDesc { name: "thread_spawn", const_idx: 29, argc: 1, has_ret: true }), + Some(SysCallDesc { name: "thread_spawn", const_idx: 29, argc: 2, has_ret: true }), Some(SysCallDesc { name: "thread_sleep", const_idx: 30, argc: 1, has_ret: false }), Some(SysCallDesc { name: "thread_join", const_idx: 31, argc: 1, has_ret: true }), ]; diff --git a/vm/src/host.rs b/vm/src/host.rs index c406311..827a884 100644 --- a/vm/src/host.rs +++ b/vm/src/host.rs @@ -98,7 +98,7 @@ pub fn get_syscall(const_idx: u16) -> HostFn MEMCPY => HostFn::Fn3_0(memcpy), MEMCMP => HostFn::Fn3_1(memcmp), - THREAD_SPAWN => HostFn::Fn1_1(thread_spawn), + THREAD_SPAWN => HostFn::Fn2_1(thread_spawn), THREAD_JOIN => HostFn::Fn1_1(thread_join), THREAD_ID => HostFn::Fn0_1(thread_id), THREAD_SLEEP => HostFn::Fn1_0(thread_sleep), @@ -154,10 +154,10 @@ fn thread_sleep(thread: &mut Thread, msecs: Value) // Spawn a new thread // Takes a function to call as argument // Returns a thread id -fn thread_spawn(thread: &mut Thread, fun: Value) -> Value +fn thread_spawn(thread: &mut Thread, fun: Value, arg: Value) -> Value { let callee_pc = fun.as_u64(); - let tid = VM::spawn_thread(&thread.vm, callee_pc, vec![]); + let tid = VM::spawn_thread(&thread.vm, callee_pc, vec![arg]); Value::from(tid) } From 22422fb5f3069fa4fafa54e967e3e19545763063 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Wed, 4 Sep 2024 17:00:21 -0400 Subject: [PATCH 66/80] Update syscall description --- api/syscalls.json | 2 +- doc/syscalls.md | 2 +- ncc/include/uvm/syscalls.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/syscalls.json b/api/syscalls.json index 3ccdf2d..548572f 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -721,7 +721,7 @@ ], "permission": "audio_output", "const_idx": 18, - "description": "Open an audio output device." + "description": "Open an audio output device, then spawn a new thread which will regularly call the specified callback function to generate audio samples." } ], "constants": [ diff --git a/doc/syscalls.md b/doc/syscalls.md index 8c39931..b49059f 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -292,7 +292,7 @@ u32 audio_open_output(u32 sample_rate, u16 num_channels, u16 format, void* callb **Returns:** `u32 device_id` -Open an audio output device. +Open an audio output device, then spawn a new thread which will regularly call the specified callback function to generate audio samples. ## Constants These are the constants associated with the audio subsystem: diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index 47527bc..9d92568 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -90,7 +90,7 @@ #define window_wait_event(__p_event) asm (__p_event) -> void { syscall window_wait_event; } // u32 audio_open_output(u32 sample_rate, u16 num_channels, u16 format, void* callback) -// Open an audio output device. +// Open an audio output device, then spawn a new thread which will regularly call the specified callback function to generate audio samples. #define audio_open_output(__sample_rate, __num_channels, __format, __callback) asm (__sample_rate, __num_channels, __format, __callback) -> u32 { syscall audio_open_output; } // u64 net_listen(const char* listen_addr, void* on_new_conn) From 9165d3f2016550f1e93f183fa5d40807388270a2 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Thu, 5 Sep 2024 00:57:40 -0400 Subject: [PATCH 67/80] Convert exit to syscall instead of VM instruction --- api/syscalls.json | 16 ++++++ doc/syscalls.md | 8 +++ ncc/include/assert.h | 2 +- ncc/include/stdlib.h | 3 +- ncc/include/uvm/syscalls.h | 4 ++ ncc/src/codegen.rs | 4 +- vm/examples/empty.asm | 2 +- vm/examples/factorial.asm | 2 +- vm/examples/fib.asm | 2 +- vm/examples/fizzbuzz.asm | 2 +- vm/examples/guess.asm | 2 +- vm/examples/loop.asm | 2 +- vm/examples/memcpy.asm | 2 +- vm/src/asm.rs | 3 +- vm/src/constants.rs | 3 +- vm/src/host.rs | 7 +++ vm/src/vm.rs | 99 ++++++++++++++++---------------------- 17 files changed, 91 insertions(+), 72 deletions(-) diff --git a/api/syscalls.json b/api/syscalls.json index 548572f..c776b16 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -126,6 +126,22 @@ "const_idx": 17, "description": "Grow the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. If the requested size is smaller than the current heap size, this is a no-op. Returns the new heap size in bytes." }, + { + "name": "exit", + "args": [ + [ + "i8", + "status" + ] + ], + "returns": [ + "void", + "" + ], + "permission": "default_allowed", + "const_idx": 11, + "description": "End program execution with the specified exit status." + }, { "name": "thread_spawn", "args": [ diff --git a/doc/syscalls.md b/doc/syscalls.md index b49059f..dd7d32c 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -68,6 +68,14 @@ u64 vm_grow_heap(u64 num_bytes) Grow the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. If the requested size is smaller than the current heap size, this is a no-op. Returns the new heap size in bytes. +## exit + +``` +void exit(i8 status) +``` + +End program execution with the specified exit status. + ## thread_spawn ``` diff --git a/ncc/include/assert.h b/ncc/include/assert.h index d63b3be..3d5c824 100644 --- a/ncc/include/assert.h +++ b/ncc/include/assert.h @@ -11,7 +11,7 @@ asm () -> void { syscall print_endl; };\ asm ("assert(" #test_expr ")") -> void { syscall print_str; };\ asm () -> void { syscall print_endl; };\ - asm () -> void { push -1; exit; };\ + asm () -> void { push -1; syscall exit; };\ } #else #define assert(test_val) {} diff --git a/ncc/include/stdlib.h b/ncc/include/stdlib.h index d0509da..1fcbcd4 100644 --- a/ncc/include/stdlib.h +++ b/ncc/include/stdlib.h @@ -11,9 +11,10 @@ int abs(int n) return n; } +#undef exit void exit(int status) { - asm (status) -> void { exit; }; + asm (status) -> void { syscall exit; }; } // Convert long int to string diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index 9d92568..568152d 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -29,6 +29,10 @@ // Grow the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. If the requested size is smaller than the current heap size, this is a no-op. Returns the new heap size in bytes. #define vm_grow_heap(__num_bytes) asm (__num_bytes) -> u64 { syscall vm_grow_heap; } +// void exit(i8 status) +// End program execution with the specified exit status. +#define exit(__status) asm (__status) -> void { syscall exit; } + // u64 thread_spawn(void* fptr, void* arg) // Spawn a new thread running the given function with the argument value `arg`. #define thread_spawn(__fptr, __arg) asm (__fptr, __arg) -> u64 { syscall thread_spawn; } diff --git a/ncc/src/codegen.rs b/ncc/src/codegen.rs index e161414..b9d4eb28 100644 --- a/ncc/src/codegen.rs +++ b/ncc/src/codegen.rs @@ -165,14 +165,14 @@ impl Unit out.push_str("# call the main function and then exit\n"); out.push_str("call main, 0;\n"); - out.push_str("exit;\n"); + out.push_str("ret;\n"); out.push_str("\n"); } else { // If there is no main function, the unit should just exit (do nothing) out.push_str("push 0;\n"); - out.push_str("exit;\n"); + out.push_str("ret;\n"); out.push_str("\n"); } diff --git a/vm/examples/empty.asm b/vm/examples/empty.asm index 4a00599..4342eef 100644 --- a/vm/examples/empty.asm +++ b/vm/examples/empty.asm @@ -1,2 +1,2 @@ push 0; -exit; \ No newline at end of file +ret; diff --git a/vm/examples/factorial.asm b/vm/examples/factorial.asm index 5232798..90e76b5 100644 --- a/vm/examples/factorial.asm +++ b/vm/examples/factorial.asm @@ -22,7 +22,7 @@ syscall print_i64; syscall print_endl; push 0; -exit; +ret; #### fact(n) #### FACT: diff --git a/vm/examples/fib.asm b/vm/examples/fib.asm index c340986..636221e 100644 --- a/vm/examples/fib.asm +++ b/vm/examples/fib.asm @@ -22,7 +22,7 @@ syscall print_i64; syscall print_endl; push 0; -exit; +ret; # # u64 fib(u64 n) diff --git a/vm/examples/fizzbuzz.asm b/vm/examples/fizzbuzz.asm index b6128c3..d22a393 100644 --- a/vm/examples/fizzbuzz.asm +++ b/vm/examples/fizzbuzz.asm @@ -58,4 +58,4 @@ push 101; lt_i64; # l0 < COUNT jnz LOOP; -exit; +ret; diff --git a/vm/examples/guess.asm b/vm/examples/guess.asm index fb7307c..0528f10 100644 --- a/vm/examples/guess.asm +++ b/vm/examples/guess.asm @@ -84,7 +84,7 @@ syscall print_i64; syscall print_endl; push 0; -exit; +ret; # # Read a positive integer from stdlin diff --git a/vm/examples/loop.asm b/vm/examples/loop.asm index eebe4d2..6778784 100644 --- a/vm/examples/loop.asm +++ b/vm/examples/loop.asm @@ -61,4 +61,4 @@ push MIPS_STR; syscall print_str; syscall print_endl; -exit; +ret; diff --git a/vm/examples/memcpy.asm b/vm/examples/memcpy.asm index 3d99a11..79d9607 100644 --- a/vm/examples/memcpy.asm +++ b/vm/examples/memcpy.asm @@ -73,4 +73,4 @@ push FPS_STR; syscall print_str; syscall print_endl; -exit; +ret; diff --git a/vm/src/asm.rs b/vm/src/asm.rs index 566b9f8..321c382 100644 --- a/vm/src/asm.rs +++ b/vm/src/asm.rs @@ -1169,7 +1169,6 @@ impl Assembler } "ret" => self.code.push_op(Op::ret), - "exit" => self.code.push_op(Op::exit), _ => { return input.parse_error(&format!("unknown instruction opcode \"{}\"", op_name)) @@ -1323,7 +1322,7 @@ mod tests parse_ok(" FOO_BAR: jmp FOO_BAR; "); // Callback label - parse_ok("CB: ret; push_p32 CB; exit;"); + parse_ok("CB: ret; push_p32 CB; ret;"); } #[test] diff --git a/vm/src/constants.rs b/vm/src/constants.rs index b4c2a0e..fb87a41 100644 --- a/vm/src/constants.rs +++ b/vm/src/constants.rs @@ -17,6 +17,7 @@ pub const PRINT_ENDL: u16 = 7; pub const GETCHAR: u16 = 8; pub const WINDOW_POLL_EVENT: u16 = 9; pub const WINDOW_DRAW_FRAME: u16 = 10; +pub const EXIT: u16 = 11; pub const VM_HEAP_SIZE: u16 = 14; pub const MEMSET32: u16 = 16; pub const VM_GROW_HEAP: u16 = 17; @@ -54,7 +55,7 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ Some(SysCallDesc { name: "getchar", const_idx: 8, argc: 0, has_ret: true }), Some(SysCallDesc { name: "window_poll_event", const_idx: 9, argc: 1, has_ret: true }), Some(SysCallDesc { name: "window_draw_frame", const_idx: 10, argc: 2, has_ret: false }), - None, + Some(SysCallDesc { name: "exit", const_idx: 11, argc: 1, has_ret: false }), None, None, Some(SysCallDesc { name: "vm_heap_size", const_idx: 14, argc: 0, has_ret: true }), diff --git a/vm/src/host.rs b/vm/src/host.rs index 827a884..fb92314 100644 --- a/vm/src/host.rs +++ b/vm/src/host.rs @@ -97,6 +97,7 @@ pub fn get_syscall(const_idx: u16) -> HostFn MEMSET32 => HostFn::Fn3_0(memset32), MEMCPY => HostFn::Fn3_0(memcpy), MEMCMP => HostFn::Fn3_1(memcmp), + EXIT => HostFn::Fn1_0(exit), THREAD_SPAWN => HostFn::Fn2_1(thread_spawn), THREAD_JOIN => HostFn::Fn1_1(thread_join), @@ -224,6 +225,12 @@ fn memcmp(thread: &mut Thread, ptr_a: Value, ptr_b: Value, num_bytes: Value) -> } } +// End program execution +fn exit(thread: &mut Thread, val: Value) +{ + unsafe { libc::exit(val.as_i32() & 0xFF) }; +} + fn print_i64(thread: &mut Thread, v: Value) { let v = v.as_i64(); diff --git a/vm/src/vm.rs b/vm/src/vm.rs index c9a9334..cba3619 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -263,16 +263,10 @@ pub enum Op // syscall (arg0, arg1, ..., argN) syscall, - // Return to caller function, or - // Return to the UVM event loop without terminating execution + // Return to caller function or end thread // ret (value) ret, - // Terminate program execution - // This stops the UVM event loop - // exit (value) - exit, - // NOTE: last opcode must have value < 255 // Currently, every opcode is just one byte long, // and we hope to keep it that way, but the value @@ -1605,17 +1599,6 @@ impl Thread } } - Op::exit => { - if self.stack.len() <= bp { - panic!("exit with no return value on stack"); - } - - let val = self.pop(); - self.stack.clear(); - self.frames.clear(); - return val; - } - Op::ret => { if self.stack.len() <= bp { panic!("ret with no return value on stack"); @@ -1813,143 +1796,143 @@ mod tests fn test_opcodes() { // We can have at most 254 short single-byte opcodes - assert!(Op::exit as usize <= 254); + assert!(Op::ret as usize <= 254); // Keep track of how many short opcodes we have so far - dbg!(Op::exit as usize); - assert!(Op::exit as usize <= 115); + dbg!(Op::ret as usize); + assert!(Op::ret as usize <= 114); } #[test] fn test_basics() { // Integer literals - eval_i64("push_i8 1; exit;", 1); - eval_i64("push_i8 -3; exit;", -3); - eval_i64("push_u64 1_333_444; exit;", 1_333_444); - eval_i64("push_u64 0xFF; exit;", 0xFF); - eval_i64("push_u64 0b1101; exit;", 0b1101); + eval_i64("push_i8 1; ret;", 1); + eval_i64("push_i8 -3; ret;", -3); + eval_i64("push_u64 1_333_444; ret;", 1_333_444); + eval_i64("push_u64 0xFF; ret;", 0xFF); + eval_i64("push_u64 0b1101; ret;", 0b1101); // Push mnemonic - eval_i64("push 0; exit;", 0); - eval_i64("push 1; exit;", 1); - eval_i64("push -1; exit;", -1); - eval_i64("push 0xFFFF; exit;", 0xFFFF); - eval_i64(".data; LABEL: .u64 0; .code; push LABEL; exit;", 0); + eval_i64("push 0; ret;", 0); + eval_i64("push 1; ret;", 1); + eval_i64("push -1; ret;", -1); + eval_i64("push 0xFFFF; ret;", 0xFFFF); + eval_i64(".data; LABEL: .u64 0; .code; push LABEL; ret;", 0); // Stack manipulation - eval_i64("push_i8 7; push_i8 3; swap; exit;", 7); - eval_i64("push_i8 7; push_i8 3; swap; swap; pop; exit;", 7); + eval_i64("push_i8 7; push_i8 3; swap; ret;", 7); + eval_i64("push_i8 7; push_i8 3; swap; swap; pop; ret;", 7); // Integer arithmetic - eval_i64("push_i8 1; push_i8 10; add_u64; exit;", 11); - eval_i64("push_i8 5; push_i8 10; sub_u64; exit;", -5); - eval_i64("push_i8 10; push_i8 2; sub_u64; exit;", 8); - eval_i64("push 5; push_i8 -6; mul_u64; exit;", -30); - eval_i64("push 1; push 2; lshift_u64; exit;", 4); + eval_i64("push_i8 1; push_i8 10; add_u64; ret;", 11); + eval_i64("push_i8 5; push_i8 10; sub_u64; ret;", -5); + eval_i64("push_i8 10; push_i8 2; sub_u64; ret;", 8); + eval_i64("push 5; push_i8 -6; mul_u64; ret;", -30); + eval_i64("push 1; push 2; lshift_u64; ret;", 4); // Comparisons - eval_i64("push_i8 1; push_i8 10; lt_i64; exit;", 1); - eval_i64("push_i8 11; push_i8 1; lt_i64; exit;", 0); + eval_i64("push_i8 1; push_i8 10; lt_i64; ret;", 1); + eval_i64("push_i8 11; push_i8 1; lt_i64; ret;", 0); } #[test] fn test_setlocal() { - eval_i64(".code; push 0; push 77; set_local 0; get_local 0; exit;", 77); + eval_i64(".code; push 0; push 77; set_local 0; get_local 0; ret;", 77); } #[test] fn test_floats() { - eval_i64("push_f32 1.5; push_f32 2.5; add_f32; push_f32 4.0; eq_u64; exit;", 1); + eval_i64("push_f32 1.5; push_f32 2.5; add_f32; push_f32 4.0; eq_u64; ret;", 1); } #[test] fn test_loop() { // Simple loop - eval_i64("push_i8 0; LOOP: push_i8 1; add_u64; dup; push_i8 10; eq_u64; jz LOOP; exit;", 10); + eval_i64("push_i8 0; LOOP: push_i8 1; add_u64; dup; push_i8 10; eq_u64; jz LOOP; ret;", 10); } #[test] fn test_load_store() { // Store instruction - eval_i64(".data; .zero 255; .code; push_i8 0; push_i8 77; store_u8; push_i8 11; exit;", 11); + eval_i64(".data; .zero 255; .code; push_i8 0; push_i8 77; store_u8; push_i8 11; ret;", 11); } #[test] fn test_setn() { // Store instruction - eval_i64(".code; push 3; push 0; push 7; setn 1; pop; exit;", 7); + eval_i64(".code; push 3; push 0; push 7; setn 1; pop; ret;", 7); } #[test] fn test_call_ret() { - eval_i64("call FN, 0; exit; FN: push_i8 33; ret;", 33); - eval_i64("push_i8 3; call FN, 1; exit; FN: get_arg 0; push_i8 1; add_u64; ret;", 4); + eval_i64("call FN, 0; ret; FN: push_i8 33; ret;", 33); + eval_i64("push_i8 3; call FN, 1; ret; FN: get_arg 0; push_i8 1; add_u64; ret;", 4); // set_arg - eval_i64("push_i8 3; call FN, 1; exit; FN: push 7; set_arg 0; get_arg 0; ret;", 7); + eval_i64("push_i8 3; call FN, 1; ret; FN: push 7; set_arg 0; get_arg 0; ret;", 7); // Two arguments and subtract (order of arguments matters) - eval_i64("push_i8 7; push 5; call FN, 2; exit; FN: get_arg 0; get_arg 1; sub_u64; ret;", 2); + eval_i64("push_i8 7; push 5; call FN, 2; ret; FN: get_arg 0; get_arg 1; sub_u64; ret;", 2); // Recursive decrement function - eval_i64("push 10; call DEC, 1; exit; DEC: get_arg 0; dup; jz ZERO; push 1; sub_u64; call DEC, 1; ret; ZERO: ret;", 0); + eval_i64("push 10; call DEC, 1; ret; DEC: get_arg 0; dup; jz ZERO; push 1; sub_u64; call DEC, 1; ret; ZERO: ret;", 0); // Regression: stack corruption - eval_i64("push 5; call foo, 0; pop; exit; foo: push 2; push 0; ret;", 5); + eval_i64("push 5; call foo, 0; pop; ret; foo: push 2; push 0; ret;", 5); } #[test] fn test_call_fp() { - eval_i64(" push FN; call_fp 0; exit; FN: push_i8 33; ret;", 33); + eval_i64(" push FN; call_fp 0; ret; FN: push_i8 33; ret;", 33); } #[test] fn test_syscalls() { - eval_src(".data; LABEL: .zero 256; .code; push LABEL; push 255; push 0; syscall memset; push 0; exit;"); + eval_src(".data; LABEL: .zero 256; .code; push LABEL; push 255; push 0; syscall memset; push 0; ret;"); } #[test] #[should_panic] fn test_div_zero() { - eval_src("push 8; push 0; div_u64; exit;"); + eval_src("push 8; push 0; div_u64; ret;"); } #[test] #[should_panic] fn test_ret_none() { - eval_src("call FN, 0; exit; FN: ret;"); + eval_src("call FN, 0; ret; FN: ret;"); } #[test] #[should_panic] fn test_get_arg_none() { - eval_src("call FN, 0; exit; FN: get_arg 0; push 0; ret;"); + eval_src("call FN, 0; ret; FN: get_arg 0; push 0; ret;"); } #[test] #[should_panic] fn test_load_oob() { - eval_src(".data; .fill 1000, 0; .code; push 100_000_000; load_u64; exit;"); + eval_src(".data; .fill 1000, 0; .code; push 100_000_000; load_u64; ret;"); } #[test] #[should_panic] fn test_memset_oob() { - eval_src(".data; LABEL: .zero 1; .code; push LABEL; push 255; push 100_000_000; syscall memset; push 0; exit;"); + eval_src(".data; LABEL: .zero 1; .code; push LABEL; push 255; push 100_000_000; syscall memset; push 0; ret;"); } // Regression: this used to segfault From 70144e08c73a2b33935e2eee036591042addd2af Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Thu, 5 Sep 2024 11:50:16 -0400 Subject: [PATCH 68/80] Change to max 256 thread locals --- vm/src/vm.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index cba3619..73956ec 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -217,11 +217,11 @@ pub enum Op */ // Set thread-local variable - // thread_set (val) + // thread_set (val) thread_set, // Get thread-local variable - // thread_get + // thread_get thread_get, // NOTE: may want to wait for this because it's not RISC, @@ -1443,7 +1443,7 @@ impl Thread } Op::thread_set => { - let idx = self.code.read_pc::(&mut pc) as usize; + let idx = self.code.read_pc::(&mut pc) as usize; let val = self.pop(); if idx >= self.locals.len() { @@ -1454,7 +1454,7 @@ impl Thread } Op::thread_get => { - let idx = self.code.read_pc::(&mut pc) as usize; + let idx = self.code.read_pc::(&mut pc) as usize; if idx >= self.locals.len() { self.push(Value::from(0)); From 89f245e5fb5ea442994d666fda1e1fcbec17055e Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Thu, 5 Sep 2024 12:55:00 -0400 Subject: [PATCH 69/80] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ac7ad65..ed0d80b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # UVM **NOTE: this project is very much a work in progress. You're likely to run -into bugs and missing features. I'm looking for collaborators who share the vision +into bugs and missing features. I would like to find collaborators who share the vision and want to help me make it happen.**

@@ -10,10 +10,10 @@ and want to help me make it happen.**

-A minimalistic virtual machine designed to run self-contained applications. UVM is intended as a platform to distribute -programs that will not break and to combat code rot. It also aims to be conceptually simple, easy to understand, easy -to target, fun to work with and approachable to newcomers. It may also be valuable as a teaching tool. There is a short -4-minute [overview of UVM](https://www.youtube.com/watch?v=q9-o45B_qsA) +A simple, minimalistic virtual machine designed to run self-contained applications. UVM is intended as a platform to distribute +programs that will not break and to combat code rot. It aims to be conceptually simple, easy to understand, easy +to target, fun to work with and approachable to newcomers. It may also be valuable as a teaching tool or as a platform +to experiment with. There is a short 4-minute [overview of UVM](https://www.youtube.com/watch?v=q9-o45B_qsA) on YouTube if you'd like to see a quick survey. Contents: From af2aef5a66b84978057246c255969c64789ca41e Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Thu, 5 Sep 2024 13:10:08 -0400 Subject: [PATCH 70/80] Update planning.md --- doc/planning.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/planning.md b/doc/planning.md index f64be76..c74c63d 100644 --- a/doc/planning.md +++ b/doc/planning.md @@ -40,10 +40,10 @@ UVM has been [designed](/doc/design.md) with JIT compilation in mind, that is, we've made multiple design choices that we think will make it easier to generate efficient machine code from our bytecode instructions. We believe it should be possible to get good performance with a fairly -simple JIT compiler. A speedup of 20x or more over the interpreter -should be expected, and hopefully near-native performance. +simple JIT compiler. A speedup of ~20x over the interpreter +should be achievable, and hopefully near-native performance. -We don't want to stat working on the JIT compiler very early in the +We don't want to start working on the JIT compiler very early in the prototype stage, because it's easier to quickly iterate over the design while working with an interpreter, but experimentation with JIT compilation needs to happen before we stabilize the current design. From eafc62c633865c32539114c5eb2d91f469e6b1b6 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 7 Sep 2024 01:31:26 -0400 Subject: [PATCH 71/80] Implement basic atomic instructions --- vm/src/vm.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 73956ec..246ec00 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -1,4 +1,5 @@ use std::sync::{Arc, Mutex}; +use std::sync::atomic::{Ordering, AtomicU64}; use std::mem::{transmute, size_of, align_of}; use std::collections::{HashSet, HashMap}; use std::thread; @@ -216,6 +217,22 @@ pub enum Op load_global_u64 */ + // Atomic load with acquire semantics + // atomic_load (addr) + atomic_load_u64, + + // Atomic store with release semantics + // atomic_store (addr) (value) + atomic_store_u64, + + // Compare-and-swap + // Uses acquire semantics on success, relaxed on failure. + // Store has relaxed semantics. + // This instruction can be used to implement spin locks. + // atomic_cas (addr) (cmp-val) (store-val) + // Pushes the value found at the memory address + atomic_cas_u64, + // Set thread-local variable // thread_set (val) thread_set, @@ -1442,6 +1459,49 @@ impl Thread unsafe { *heap_ptr = val; } } + Op::atomic_load_u64 => { + let addr = self.pop().as_usize(); + let heap_ptr = self.get_heap_ptr_mut(addr, 1); + let atomic = unsafe {AtomicU64::from_ptr(heap_ptr) }; + let val = atomic.load(Ordering::Acquire); + self.push(Value::from(val)); + } + + Op::atomic_store_u64 => { + let val = self.pop().as_u64(); + let addr = self.pop().as_usize(); + let heap_ptr = self.get_heap_ptr_mut(addr, 1); + let atomic = unsafe {AtomicU64::from_ptr(heap_ptr) }; + atomic.store(1, Ordering::Release); + } + + // Compare-and-swap + // Uses acquire semantics on success, relaxed on failure. + // Store has relaxed semantics. + // This instruction can be used to implement spin locks. + // atomic_cas (addr) (cmp-val) (store-val) + // Pushes the value found at the memory address + Op::atomic_cas_u64 => { + let addr = self.pop().as_usize(); + let cmp_val = self.pop().as_u64(); + let store_val = self.pop().as_u64(); + + let heap_ptr = self.get_heap_ptr_mut(addr, 1); + let atomic = unsafe {AtomicU64::from_ptr(heap_ptr) }; + + let result = atomic.compare_exchange( + cmp_val, + store_val, + Ordering::Acquire, + Ordering::Relaxed, + ); + + match result { + Ok(val) => self.push(Value::from(val)), + Err(actual_val) => self.push(Value::from(actual_val)), + } + } + Op::thread_set => { let idx = self.code.read_pc::(&mut pc) as usize; let val = self.pop(); @@ -1800,7 +1860,7 @@ mod tests // Keep track of how many short opcodes we have so far dbg!(Op::ret as usize); - assert!(Op::ret as usize <= 114); + assert!(Op::ret as usize <= 117); } #[test] From 46924ac5dd5487cd891730c3bcc70aee4138d31d Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Sat, 7 Sep 2024 03:11:40 -0400 Subject: [PATCH 72/80] Add test for threads and atomic operations --- ncc/src/codegen.rs | 7 +++++ ncc/tests/threads_lock.c | 58 ++++++++++++++++++++++++++++++++++++++++ vm/src/asm.rs | 4 +++ vm/src/vm.rs | 11 ++++---- 4 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 ncc/tests/threads_lock.c diff --git a/ncc/src/codegen.rs b/ncc/src/codegen.rs index b9d4eb28..bdc7551 100644 --- a/ncc/src/codegen.rs +++ b/ncc/src/codegen.rs @@ -632,6 +632,13 @@ impl Expr child.gen_code(sym, out)?; return Ok(()); } + + // If this is a reference to a global + if let Expr::Ref(Decl::Global { name, t }) = child.as_ref() { + // Push the address + out.push_str(&format!("push {};\n", name)); + return Ok(()); + } } child.gen_code(sym, out)?; diff --git a/ncc/tests/threads_lock.c b/ncc/tests/threads_lock.c new file mode 100644 index 0000000..b12d1cd --- /dev/null +++ b/ncc/tests/threads_lock.c @@ -0,0 +1,58 @@ +#include +#include + +#define NUM_THREADS 100 +#define NUM_INCRS 100 + +u64 thread_ids[NUM_THREADS]; + +u64 locked = 0; + +u64 counter = 0; + +void lock() +{ + for (;;) + { + // Try to acquire the lock + u64 val = asm (&locked, 0, 1) -> u64 { atomic_cas_u64; }; + + // If we got the lock, stop + if (val == 0) + break; + } +} + +void unlock() +{ + asm (&locked, 0) -> void { atomic_store_u64; }; +} + +void thread_fn() +{ + for (int i = 0; i < NUM_INCRS; ++i) + { + lock(); + ++counter; + unlock(); + } +} + +int main() +{ + for (int i = 0; i < NUM_THREADS; ++i) + { + thread_ids[i] = thread_spawn(thread_fn, NULL); + } + + for (int i = 0; i < NUM_THREADS; ++i) + { + thread_join(thread_ids[i]); + } + + // Check that the counter value is correct + assert(counter == NUM_THREADS * NUM_INCRS); + printf("counter = %d\n", counter); + + return 0; +} diff --git a/vm/src/asm.rs b/vm/src/asm.rs index 321c382..51481fe 100644 --- a/vm/src/asm.rs +++ b/vm/src/asm.rs @@ -1111,6 +1111,10 @@ impl Assembler "store_u32" => self.code.push_op(Op::store_u32), "store_u64" => self.code.push_op(Op::store_u64), + "atomic_load_u64" => self.code.push_op(Op::atomic_load_u64), + "atomic_store_u64" => self.code.push_op(Op::atomic_store_u64), + "atomic_cas_u64" => self.code.push_op(Op::atomic_cas_u64), + "jmp" => { self.code.push_op(Op::jmp); let label_name = input.parse_ident()?; diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 246ec00..3282795 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -582,8 +582,9 @@ impl MemView // Check that the address is aligned if addr & (align_of::() - 1) != 0 { panic!( - "attempting to access data of type {} at unaligned address", - std::any::type_name::() + "attempting to access data of type {} at unaligned address {}", + std::any::type_name::(), + addr ); } @@ -1472,7 +1473,7 @@ impl Thread let addr = self.pop().as_usize(); let heap_ptr = self.get_heap_ptr_mut(addr, 1); let atomic = unsafe {AtomicU64::from_ptr(heap_ptr) }; - atomic.store(1, Ordering::Release); + atomic.store(val, Ordering::Release); } // Compare-and-swap @@ -1482,9 +1483,9 @@ impl Thread // atomic_cas (addr) (cmp-val) (store-val) // Pushes the value found at the memory address Op::atomic_cas_u64 => { - let addr = self.pop().as_usize(); - let cmp_val = self.pop().as_u64(); let store_val = self.pop().as_u64(); + let cmp_val = self.pop().as_u64(); + let addr = self.pop().as_usize(); let heap_ptr = self.get_heap_ptr_mut(addr, 1); let atomic = unsafe {AtomicU64::from_ptr(heap_ptr) }; From 335ef6514d77beaa6b5dbf1472e7888c8d2cad5b Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 10 Sep 2024 11:00:09 -0400 Subject: [PATCH 73/80] Implement audio_open_input. Fix ball.c sound output. --- api/syscalls.json | 28 ++++++++++ doc/syscalls.md | 10 ++++ ncc/examples/ball.c | 10 ++-- ncc/include/uvm/syscalls.h | 4 ++ vm/src/audio.rs | 108 +++++++++++++++++++++++++++++++------ vm/src/constants.rs | 3 +- vm/src/window.rs | 8 +++ 7 files changed, 150 insertions(+), 21 deletions(-) diff --git a/api/syscalls.json b/api/syscalls.json index c776b16..cb88b01 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -738,6 +738,34 @@ "permission": "audio_output", "const_idx": 18, "description": "Open an audio output device, then spawn a new thread which will regularly call the specified callback function to generate audio samples." + }, + { + "name": "audio_open_input", + "args": [ + [ + "u32", + "sample_rate" + ], + [ + "u16", + "num_channels" + ], + [ + "u16", + "format" + ], + [ + "void*", + "callback" + ] + ], + "returns": [ + "u32", + "device_id" + ], + "permission": "audio_input", + "const_idx": 12, + "description": "Open an audio input device, then spawn a new thread which will regularly call the specified callback function to process audio samples." } ], "constants": [ diff --git a/doc/syscalls.md b/doc/syscalls.md index dd7d32c..9113bbe 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -302,6 +302,16 @@ u32 audio_open_output(u32 sample_rate, u16 num_channels, u16 format, void* callb Open an audio output device, then spawn a new thread which will regularly call the specified callback function to generate audio samples. +## audio_open_input + +``` +u32 audio_open_input(u32 sample_rate, u16 num_channels, u16 format, void* callback) +``` + +**Returns:** `u32 device_id` + +Open an audio input device, then spawn a new thread which will regularly call the specified callback function to process audio samples. + ## Constants These are the constants associated with the audio subsystem: diff --git a/ncc/examples/ball.c b/ncc/examples/ball.c index 9a5d69b..94a463f 100644 --- a/ncc/examples/ball.c +++ b/ncc/examples/ball.c @@ -90,7 +90,6 @@ void update() window_draw_frame(0, frame_buffer); } -/* u16* audio_cb(u16 num_channels, u32 num_samples) { assert(num_channels == 1); @@ -110,7 +109,9 @@ u16* audio_cb(u16 num_channels, u32 num_samples) // The intensity decreases over time u32 intensity = INT16_MAX - (audio_pos * INT16_MAX / AUDIO_LEN); - u32 sawtooth = 4000 * (i % 128) / 128; + u32 period = 128 - (audio_pos * 64 / AUDIO_LEN); + + u32 sawtooth = 4000 * (i % period) / period; AUDIO_BUFFER[i] = intensity * sawtooth / INT16_MAX; ++audio_pos; @@ -118,13 +119,12 @@ u16* audio_cb(u16 num_channels, u32 num_samples) return AUDIO_BUFFER; } -*/ void main() { window_create(FRAME_WIDTH, FRAME_HEIGHT, "Bouncing Ball Example", 0); - anim_event_loop(60, update); + audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb); - //audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb); + anim_event_loop(60, update); } diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index 568152d..115355c 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -97,6 +97,10 @@ // Open an audio output device, then spawn a new thread which will regularly call the specified callback function to generate audio samples. #define audio_open_output(__sample_rate, __num_channels, __format, __callback) asm (__sample_rate, __num_channels, __format, __callback) -> u32 { syscall audio_open_output; } +// u32 audio_open_input(u32 sample_rate, u16 num_channels, u16 format, void* callback) +// Open an audio input device, then spawn a new thread which will regularly call the specified callback function to process audio samples. +#define audio_open_input(__sample_rate, __num_channels, __format, __callback) asm (__sample_rate, __num_channels, __format, __callback) -> u32 { syscall audio_open_input; } + // u64 net_listen(const char* listen_addr, void* on_new_conn) // Open a listening TCP socket to accept incoming connections. A callback function is called when a new connection request is received. #define net_listen(__listen_addr, __on_new_conn) asm (__listen_addr, __on_new_conn) -> u64 { syscall net_listen; } diff --git a/vm/src/audio.rs b/vm/src/audio.rs index 1d97504..b1fcfc3 100644 --- a/vm/src/audio.rs +++ b/vm/src/audio.rs @@ -1,5 +1,7 @@ use sdl2::audio::{AudioCallback, AudioSpecDesired, AudioDevice}; use std::sync::{Arc, Weak, Mutex}; +use std::collections::HashMap; +use std::cell::RefCell; use crate::vm::{Value, VM, Thread}; use crate::host::{get_sdl_context}; use crate::constants::*; @@ -37,17 +39,30 @@ impl AudioCallback for AudioCB } } -// TODO: support multiple audio devices -/// We have to keep the audio device alive -/// This is a global variable because it doesn't implement -/// the Send trait, and so can't be referenced from another thread -static mut DEVICE: Option> = None; +#[derive(Default)] +struct AudioState +{ + input_dev: Option>, + output_dev: Option>, +} + +// This is only accessed from the main thread +thread_local! { + static AUDIO_STATE: RefCell = RefCell::new(AudioState::default()); +} -// NOTE: this can only be called from the main thread since it uses SDL -// However, it creates a new thread to generate audio sample, this thread -// could be given a reference to another VM instance pub fn audio_open_output(thread: &mut Thread, sample_rate: Value, num_channels: Value, format: Value, cb: Value) -> Value { + if thread.id != 0 { + panic!("audio functions should only be called from the main thread"); + } + + AUDIO_STATE.with_borrow(|s| { + if s.output_dev.is_some() { + panic!("audio output device already open"); + } + }); + let sample_rate = sample_rate.as_u32(); let num_channels = num_channels.as_u16(); let format = format.as_u16(); @@ -66,19 +81,82 @@ pub fn audio_open_output(thread: &mut Thread, sample_rate: Value, num_channels: panic!("for now, only i16, 16-bit signed audio format supported"); } + let desired_spec = AudioSpecDesired { + freq: Some(sample_rate as i32), + channels: Some(num_channels as u8), + samples: Some(1024) // buffer size, 1024 samples + }; + + // Create a new VM thread in which to run the audio callback + let audio_thread = VM::new_thread(&thread.vm); + let sdl = get_sdl_context(); let audio_subsystem = sdl.audio().unwrap(); + let device = audio_subsystem.open_playback(None, &desired_spec, |spec| { + // initialize the audio callback + AudioCB { + num_channels: num_channels.into(), + thread: audio_thread, + cb: cb, + } + }).unwrap(); + + // Start playback + device.resume(); + + // Keep the audio device alive + AUDIO_STATE.with_borrow_mut(|s| { + s.output_dev = Some(device); + }); + + // FIXME: return the device_id (u32) + Value::from(0) +} + +pub fn audio_open_input(thread: &mut Thread, sample_rate: Value, num_channels: Value, format: Value, cb: Value) -> Value +{ + if thread.id != 0 { + panic!("audio functions should only be called from the main thread"); + } + + AUDIO_STATE.with_borrow(|s| { + if s.input_dev.is_some() { + panic!("audio input device already open"); + } + }); + + let sample_rate = sample_rate.as_u32(); + let num_channels = num_channels.as_u16(); + let format = format.as_u16(); + let cb = cb.as_u64(); + + if sample_rate != 44100 { + panic!("for now, only 44100Hz sample rate suppored"); + } + + //if num_channels > 2 { + if num_channels != 1 { + panic!("for now, only one output channel supported"); + } + + if format != AUDIO_FORMAT_I16 { + panic!("for now, only i16, 16-bit signed audio format supported"); + } + let desired_spec = AudioSpecDesired { freq: Some(sample_rate as i32), - channels: Some(num_channels as u8), // mono + channels: Some(num_channels as u8), samples: Some(1024) // buffer size, 1024 samples }; // Create a new VM thread in which to run the audio callback let audio_thread = VM::new_thread(&thread.vm); - let device = audio_subsystem.open_playback(None, &desired_spec, |spec| { + let sdl = get_sdl_context(); + let audio_subsystem = sdl.audio().unwrap(); + + let device = audio_subsystem.open_capture(None, &desired_spec, |spec| { // initialize the audio callback AudioCB { num_channels: num_channels.into(), @@ -91,10 +169,10 @@ pub fn audio_open_output(thread: &mut Thread, sample_rate: Value, num_channels: device.resume(); // Keep the audio device alive - unsafe { - DEVICE = Some(device); - } + AUDIO_STATE.with_borrow_mut(|s| { + s.input_dev = Some(device); + }); - // TODO: return the device_id (u32) - Value::from(0) + // FIXME: return the device_id (u32) + Value::from(1) } diff --git a/vm/src/constants.rs b/vm/src/constants.rs index fb87a41..9559e73 100644 --- a/vm/src/constants.rs +++ b/vm/src/constants.rs @@ -18,6 +18,7 @@ pub const GETCHAR: u16 = 8; pub const WINDOW_POLL_EVENT: u16 = 9; pub const WINDOW_DRAW_FRAME: u16 = 10; pub const EXIT: u16 = 11; +pub const AUDIO_OPEN_INPUT: u16 = 12; pub const VM_HEAP_SIZE: u16 = 14; pub const MEMSET32: u16 = 16; pub const VM_GROW_HEAP: u16 = 17; @@ -56,7 +57,7 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ Some(SysCallDesc { name: "window_poll_event", const_idx: 9, argc: 1, has_ret: true }), Some(SysCallDesc { name: "window_draw_frame", const_idx: 10, argc: 2, has_ret: false }), Some(SysCallDesc { name: "exit", const_idx: 11, argc: 1, has_ret: false }), - None, + Some(SysCallDesc { name: "audio_open_input", const_idx: 12, argc: 4, has_ret: true }), None, Some(SysCallDesc { name: "vm_heap_size", const_idx: 14, argc: 0, has_ret: true }), None, diff --git a/vm/src/window.rs b/vm/src/window.rs index 8c53720..ba89c2b 100644 --- a/vm/src/window.rs +++ b/vm/src/window.rs @@ -175,6 +175,10 @@ struct CEvent /// Returns true if an event was read pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value { + if thread.id != 0 { + panic!("window functions should only be called from the main thread"); + } + let p_event = p_event.as_usize(); assert!(p_event != 0); let p_event: *mut CEvent = thread.get_heap_ptr_mut(p_event, size_of::()); @@ -196,6 +200,10 @@ pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value /// Blocks until an event is read pub fn window_wait_event(thread: &mut Thread, p_event: Value) { + if thread.id != 0 { + panic!("window functions should only be called from the main thread"); + } + let p_event = p_event.as_usize(); assert!(p_event != 0); let p_event: *mut CEvent = thread.get_heap_ptr_mut(p_event, size_of::()); From c0b195f7e2d6491c7b62fc55decadb21346413e2 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 10 Sep 2024 16:21:03 -0400 Subject: [PATCH 74/80] Split audio input and output callbacks --- ncc/examples/ball.c | 2 +- vm/src/audio.rs | 88 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/ncc/examples/ball.c b/ncc/examples/ball.c index 94a463f..b47cb86 100644 --- a/ncc/examples/ball.c +++ b/ncc/examples/ball.c @@ -10,7 +10,7 @@ #define AUDIO_LEN 8_000 // RGBA pixels: 800 * 600 -u32 frame_buffer[600][800]; +u32 frame_buffer[FRAME_HEIGHT][FRAME_WIDTH]; // Current ball position int px = 200; diff --git a/vm/src/audio.rs b/vm/src/audio.rs index b1fcfc3..84e0372 100644 --- a/vm/src/audio.rs +++ b/vm/src/audio.rs @@ -6,11 +6,15 @@ use crate::vm::{Value, VM, Thread}; use crate::host::{get_sdl_context}; use crate::constants::*; -struct AudioCB +// Audio output callback +struct OutputCB { // Number of audio output channels num_channels: usize, + // Expected buffer size + buf_size: usize, + // VM thread in which to execute the audio callback thread: Thread, @@ -18,18 +22,20 @@ struct AudioCB cb: u64, } -impl AudioCallback for AudioCB +impl AudioCallback for OutputCB { - // Signed 16-bit samples + // Using signed 16-bit samples type Channel = i16; fn callback(&mut self, out: &mut [i16]) { - out.fill(0); - let output_len = out.len(); assert!(output_len % self.num_channels == 0); let samples_per_chan = output_len / self.num_channels; + assert!(samples_per_chan == self.buf_size); + + // Clear the buffer + out.fill(0); // Run the audio callback let ptr = self.thread.call(self.cb, &[Value::from(self.num_channels), Value::from(samples_per_chan)]); @@ -39,18 +45,76 @@ impl AudioCallback for AudioCB } } +// Audio input callback +struct InputCB +{ + // Number of audio input channels + num_channels: usize, + + // Expected buffer size + buf_size: usize, + + // VM thread in which to execute the audio callback + thread: Thread, + + // Callback function pointer + cb: u64, +} + +impl AudioCallback for InputCB +{ + // Using signed 16-bit samples + type Channel = i16; + + // Receives a buffer of input samples + fn callback(&mut self, buf: &mut [i16]) + { + assert!(buf.len() % self.num_channels == 0); + let samples_per_chan = buf.len() / self.num_channels; + assert!(samples_per_chan == self.buf_size); + + // Copy the samples to make them accessible to the audio thread + INPUT_STATE.with_borrow_mut(|s| { + s.input_tid = self.thread.id; + s.samples.clear(); + s.samples.copy_from_slice(buf); + }); + + // Run the audio callback + let ptr = self.thread.call(self.cb, &[Value::from(self.num_channels), Value::from(samples_per_chan)]); + } +} + #[derive(Default)] struct AudioState { - input_dev: Option>, - output_dev: Option>, + output_dev: Option>, + input_dev: Option>, +} + +#[derive(Default)] +struct InputState +{ + // Thread doing the audio input + input_tid: u64, + + // Samples available to read + samples: Vec, } -// This is only accessed from the main thread thread_local! { + // This is only accessed from the main thread static AUDIO_STATE: RefCell = RefCell::new(AudioState::default()); + + // Audio input state. Accessed from the input thread. + static INPUT_STATE: RefCell = RefCell::new(InputState::default()); } + + + + + pub fn audio_open_output(thread: &mut Thread, sample_rate: Value, num_channels: Value, format: Value, cb: Value) -> Value { if thread.id != 0 { @@ -94,9 +158,9 @@ pub fn audio_open_output(thread: &mut Thread, sample_rate: Value, num_channels: let audio_subsystem = sdl.audio().unwrap(); let device = audio_subsystem.open_playback(None, &desired_spec, |spec| { - // initialize the audio callback - AudioCB { + OutputCB { num_channels: num_channels.into(), + buf_size: desired_spec.samples.unwrap() as usize, thread: audio_thread, cb: cb, } @@ -157,9 +221,9 @@ pub fn audio_open_input(thread: &mut Thread, sample_rate: Value, num_channels: V let audio_subsystem = sdl.audio().unwrap(); let device = audio_subsystem.open_capture(None, &desired_spec, |spec| { - // initialize the audio callback - AudioCB { + InputCB { num_channels: num_channels.into(), + buf_size: desired_spec.samples.unwrap() as usize, thread: audio_thread, cb: cb, } From 83529593ed302ce54343229cc11f6f7c0525f122 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 10 Sep 2024 18:19:53 -0400 Subject: [PATCH 75/80] Implement audio_read_samples() --- api/syscalls.json | 20 ++++++++++++++++++++ doc/syscalls.md | 8 ++++++++ ncc/include/uvm/syscalls.h | 4 ++++ vm/src/audio.rs | 23 +++++++++++++++++++++++ vm/src/constants.rs | 3 ++- vm/src/host.rs | 2 ++ 6 files changed, 59 insertions(+), 1 deletion(-) diff --git a/api/syscalls.json b/api/syscalls.json index cb88b01..7e02222 100644 --- a/api/syscalls.json +++ b/api/syscalls.json @@ -766,6 +766,26 @@ "permission": "audio_input", "const_idx": 12, "description": "Open an audio input device, then spawn a new thread which will regularly call the specified callback function to process audio samples." + }, + { + "name": "audio_read_samples", + "args": [ + [ + "i16*", + "dst_buf" + ], + [ + "u32", + "num_samples" + ] + ], + "returns": [ + "void", + "" + ], + "permission": "audio_input", + "const_idx": 13, + "description": "Read available input samples. Must be called from the audio input thread." } ], "constants": [ diff --git a/doc/syscalls.md b/doc/syscalls.md index 9113bbe..cf5367d 100644 --- a/doc/syscalls.md +++ b/doc/syscalls.md @@ -312,6 +312,14 @@ u32 audio_open_input(u32 sample_rate, u16 num_channels, u16 format, void* callba Open an audio input device, then spawn a new thread which will regularly call the specified callback function to process audio samples. +## audio_read_samples + +``` +void audio_read_samples(i16* dst_buf, u32 num_samples) +``` + +Read available input samples. Must be called from the audio input thread. + ## Constants These are the constants associated with the audio subsystem: diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h index 115355c..5b9c683 100644 --- a/ncc/include/uvm/syscalls.h +++ b/ncc/include/uvm/syscalls.h @@ -101,6 +101,10 @@ // Open an audio input device, then spawn a new thread which will regularly call the specified callback function to process audio samples. #define audio_open_input(__sample_rate, __num_channels, __format, __callback) asm (__sample_rate, __num_channels, __format, __callback) -> u32 { syscall audio_open_input; } +// void audio_read_samples(i16* dst_buf, u32 num_samples) +// Read available input samples. Must be called from the audio input thread. +#define audio_read_samples(__dst_buf, __num_samples) asm (__dst_buf, __num_samples) -> void { syscall audio_read_samples; } + // u64 net_listen(const char* listen_addr, void* on_new_conn) // Open a listening TCP socket to accept incoming connections. A callback function is called when a new connection request is received. #define net_listen(__listen_addr, __on_new_conn) asm (__listen_addr, __on_new_conn) -> u64 { syscall net_listen; } diff --git a/vm/src/audio.rs b/vm/src/audio.rs index 84e0372..e7c8b04 100644 --- a/vm/src/audio.rs +++ b/vm/src/audio.rs @@ -240,3 +240,26 @@ pub fn audio_open_input(thread: &mut Thread, sample_rate: Value, num_channels: V // FIXME: return the device_id (u32) Value::from(1) } + +/// Read audio samples from an audio input thread +pub fn audio_read_samples(thread: &mut Thread, dst_ptr: Value, num_samples: Value) +{ + let dst_ptr = dst_ptr.as_usize(); + let num_samples = num_samples.as_usize(); + + INPUT_STATE.with_borrow_mut(|s| { + if s.input_tid != thread.id { + panic!("can only read audio samples from audio input thread"); + } + + // For now, force reading all available samples + if num_samples != s.samples.len() { + panic!("must read all available samples"); + } + + let dst_buf: &mut [i16] = thread.get_heap_slice_mut(dst_ptr, num_samples); + dst_buf.copy_from_slice(&s.samples); + + s.samples.clear(); + }); +} diff --git a/vm/src/constants.rs b/vm/src/constants.rs index 9559e73..18cf369 100644 --- a/vm/src/constants.rs +++ b/vm/src/constants.rs @@ -19,6 +19,7 @@ pub const WINDOW_POLL_EVENT: u16 = 9; pub const WINDOW_DRAW_FRAME: u16 = 10; pub const EXIT: u16 = 11; pub const AUDIO_OPEN_INPUT: u16 = 12; +pub const AUDIO_READ_SAMPLES: u16 = 13; pub const VM_HEAP_SIZE: u16 = 14; pub const MEMSET32: u16 = 16; pub const VM_GROW_HEAP: u16 = 17; @@ -58,7 +59,7 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [ Some(SysCallDesc { name: "window_draw_frame", const_idx: 10, argc: 2, has_ret: false }), Some(SysCallDesc { name: "exit", const_idx: 11, argc: 1, has_ret: false }), Some(SysCallDesc { name: "audio_open_input", const_idx: 12, argc: 4, has_ret: true }), - None, + Some(SysCallDesc { name: "audio_read_samples", const_idx: 13, argc: 2, has_ret: false }), Some(SysCallDesc { name: "vm_heap_size", const_idx: 14, argc: 0, has_ret: true }), None, Some(SysCallDesc { name: "memset32", const_idx: 16, argc: 3, has_ret: false }), diff --git a/vm/src/host.rs b/vm/src/host.rs index fb92314..1513e84 100644 --- a/vm/src/host.rs +++ b/vm/src/host.rs @@ -120,6 +120,8 @@ pub fn get_syscall(const_idx: u16) -> HostFn WINDOW_WAIT_EVENT => HostFn::Fn1_0(window_wait_event), AUDIO_OPEN_OUTPUT => HostFn::Fn4_1(audio_open_output), + AUDIO_OPEN_INPUT => HostFn::Fn4_1(audio_open_input), + AUDIO_READ_SAMPLES => HostFn::Fn2_0(audio_read_samples), _ => panic!("unknown syscall \"{}\"", const_idx), } From e98bcabe7bd34f97d763e3cecb6160642adfde33 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Tue, 10 Sep 2024 20:16:24 -0400 Subject: [PATCH 76/80] Fix issue with audio_read_samples --- ncc/examples/ball.c | 4 ++-- vm/src/audio.rs | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/ncc/examples/ball.c b/ncc/examples/ball.c index b47cb86..deb821e 100644 --- a/ncc/examples/ball.c +++ b/ncc/examples/ball.c @@ -9,7 +9,7 @@ #define BALL_RADIUS 20 #define AUDIO_LEN 8_000 -// RGBA pixels: 800 * 600 +// RGBA pixels u32 frame_buffer[FRAME_HEIGHT][FRAME_WIDTH]; // Current ball position @@ -58,7 +58,7 @@ void draw_ball() void update() { // Clear the frame buffer, set all pixels to black - memset32(frame_buffer, 0, 800 * 600); + memset32(frame_buffer, 0, sizeof(frame_buffer) / sizeof(u32)); draw_ball(); diff --git a/vm/src/audio.rs b/vm/src/audio.rs index e7c8b04..cee944f 100644 --- a/vm/src/audio.rs +++ b/vm/src/audio.rs @@ -76,7 +76,7 @@ impl AudioCallback for InputCB // Copy the samples to make them accessible to the audio thread INPUT_STATE.with_borrow_mut(|s| { s.input_tid = self.thread.id; - s.samples.clear(); + s.samples.resize(buf.len(), 0); s.samples.copy_from_slice(buf); }); @@ -110,11 +110,6 @@ thread_local! { static INPUT_STATE: RefCell = RefCell::new(InputState::default()); } - - - - - pub fn audio_open_output(thread: &mut Thread, sample_rate: Value, num_channels: Value, format: Value, cb: Value) -> Value { if thread.id != 0 { From 387e824742d2e3d26d6e38ac7fa07408a2a8eb84 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Wed, 11 Sep 2024 20:12:40 -0400 Subject: [PATCH 77/80] Add audio_graph.c example --- ncc/examples/audio_graph.c | 107 +++++++++++++++++++++++++++++++++++++ ncc/examples/ball.c | 1 - 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 ncc/examples/audio_graph.c diff --git a/ncc/examples/audio_graph.c b/ncc/examples/audio_graph.c new file mode 100644 index 0000000..adb6fcb --- /dev/null +++ b/ncc/examples/audio_graph.c @@ -0,0 +1,107 @@ +#include +#include +#include +#include +#include +#include +#include + +#define FRAME_WIDTH 600 +#define FRAME_HEIGHT 200 +#define SAMPLE_RATE 44100 +#define DISP_SAMPLES 176400 // SAMPLE_RATE * 4 + +// RGBA pixels +u32 frame_buffer[FRAME_HEIGHT][FRAME_WIDTH]; + +// Buffer for incoming samples +i16 buffer[1024]; + +// Buffer for display +i16 disp_samples[DISP_SAMPLES]; + +// Current recording position +size_t rec_pos = 0; + +void update() +{ + // Clear the frame buffer, set all pixels to black + memset32(frame_buffer, 0, sizeof(frame_buffer) / sizeof(u32)); + + int prev_y = FRAME_HEIGHT / 2; + + for (size_t x = 1; x < FRAME_WIDTH; ++x) + { + size_t sample_idx = x * DISP_SAMPLES / FRAME_WIDTH; + + // Bring sample into the [-1, 1] range + float sample = (float)disp_samples[sample_idx] / (INT16_MAX + 1); + + // Bring the sample into the [0, 1] range + sample = (sample + 1.0f) / 2; + + int y = (int)(sample * (FRAME_HEIGHT - 1)); + + draw_line( + (u32*)&frame_buffer, + FRAME_WIDTH, + FRAME_HEIGHT, + x - 1, + prev_y, + x, + y, + COLOR_RED, + ); + + prev_y = y; + } + + // Draw vertical line at recording position + u32 rec_x = rec_pos * FRAME_WIDTH / DISP_SAMPLES; + draw_line( + (u32*)&frame_buffer, + FRAME_WIDTH, + FRAME_HEIGHT, + rec_x, + 0, + rec_x, + FRAME_HEIGHT - 1, + COLOR_WHITE, + ); + + window_draw_frame(0, frame_buffer); +} + +void audio_cb(u16 num_channels, u32 num_samples) +{ + assert(num_channels == 1); + assert(num_samples <= 1024); + + audio_read_samples(&buffer, num_samples); + + size_t end_pos = MIN(rec_pos + num_samples, sizeof(disp_samples) / sizeof(i16)); + size_t num_copy = end_pos - rec_pos; + + memcpy(&disp_samples[rec_pos], &buffer, num_copy * sizeof(i16)); + + rec_pos = (rec_pos + num_copy) % DISP_SAMPLES; + + if (num_copy < num_samples) + { + size_t buf_pos = num_copy; + size_t num_copy = num_samples - buf_pos; + + memcpy(&disp_samples, &buffer[buf_pos], num_copy * sizeof(i16)); + rec_pos = num_copy; + } + + //printf("%d\n", rec_pos); +} + +void main() +{ + window_create(FRAME_WIDTH, FRAME_HEIGHT, "Audio Input Graph", 0); + audio_open_input(SAMPLE_RATE, 1, AUDIO_FORMAT_I16, audio_cb); + + anim_event_loop(30, update); +} diff --git a/ncc/examples/ball.c b/ncc/examples/ball.c index deb821e..e769cb1 100644 --- a/ncc/examples/ball.c +++ b/ncc/examples/ball.c @@ -1,5 +1,4 @@ #include -#include #include #include #include From 5120b4f22e6342bef9c54d8927b5ed1740ad042a Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Wed, 11 Sep 2024 21:13:09 -0400 Subject: [PATCH 78/80] Enable vsync to avoid tearing --- vm/src/window.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vm/src/window.rs b/vm/src/window.rs index ba89c2b..745c972 100644 --- a/vm/src/window.rs +++ b/vm/src/window.rs @@ -86,7 +86,7 @@ pub fn window_create(thread: &mut Thread, width: Value, height: Value, title: Va .build() .unwrap(); - let mut canvas = window.into_canvas().build().unwrap(); + let mut canvas = window.into_canvas().present_vsync().build().unwrap(); canvas.set_draw_color(Color::RGB(0, 0, 0)); canvas.clear(); @@ -140,6 +140,9 @@ pub fn window_draw_frame(thread: &mut Thread, window_id: Value, src_addr: Value) window.canvas.window_mut().raise(); } + // Clear the canvas + window.canvas.clear(); + // Update the texture let pitch = 4 * window.width as usize; let pixel_slice = unsafe { std::slice::from_raw_parts(data_ptr, data_len) }; From 7925154f8c5d9a63efccd94cff26f63e8d69fd1d Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Thu, 12 Sep 2024 01:12:40 -0400 Subject: [PATCH 79/80] Improve error message --- ncc/src/parser.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ncc/src/parser.rs b/ncc/src/parser.rs index eb2a497..3890320 100644 --- a/ncc/src/parser.rs +++ b/ncc/src/parser.rs @@ -39,9 +39,9 @@ fn parse_atom(input: &mut Input) -> Result let float_val: f32 = num_str.parse().unwrap(); if !input.match_char('f') { - return input.parse_error(&concat!(" - only floats are supported for now, ", - "e.g. 3.5f (float), not 3.5 (double)" + return input.parse_error(&format!( + "only floats are supported for now, try {}f (float) instead of {} (double)", + num_str, num_str )); } From 778114c4adf9a928a77c3491bb8dd3c1e464fd84 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Fri, 13 Sep 2024 15:00:16 -0400 Subject: [PATCH 80/80] Improve compiler error message --- ncc/include/stdio.h | 3 --- ncc/src/symbols.rs | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ncc/include/stdio.h b/ncc/include/stdio.h index 348e3cc..1437565 100644 --- a/ncc/include/stdio.h +++ b/ncc/include/stdio.h @@ -26,9 +26,6 @@ int getchar() } #endif -// Internal buffer used by printf -char* __buffer[32]; - int printf(char* format, ...) { unsigned int ch_written = 0; diff --git a/ncc/src/symbols.rs b/ncc/src/symbols.rs index 4e244bc..166308d 100644 --- a/ncc/src/symbols.rs +++ b/ncc/src/symbols.rs @@ -93,6 +93,14 @@ impl Env offset } + /// Check if a local with this name is already defined + fn local_defined(&self, name: &str) -> bool + { + let num_scopes = self.scopes.len(); + let top_scope = &self.scopes[num_scopes - 1]; + top_scope.decls.get(name).is_some() + } + /// Define a new local variable in the topmost scope fn define_local(&mut self, name: &str, var_type: Type) { @@ -330,6 +338,13 @@ impl Stmt // Local variable declaration Stmt::VarDecl { var_type, var_name, init_expr } => { + if env.local_defined(var_name) { + return ParseError::msg_only(&format!( + "local with name \"{}\" already exists", + var_name + )); + } + env.define_local(var_name, var_type.clone()); let decl = env.lookup(var_name).unwrap();