diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba4ca27..a8e938d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,8 +14,8 @@ jobs: - name: Cache Rust uses: Swatinem/rust-cache@v2 - name: Run tests - run: just test + run: source bin/activate-hermit && just test - name: Build extern demo - run: just extern-demo-build + run: source bin/activate-hermit && just extern-demo-build - name: Run extern demo - run: just extern-demo-run + run: source bin/activate-hermit && just extern-demo-run diff --git a/TUTORIAL.md b/TUTORIAL.md index 4508673..6e6722a 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -229,7 +229,7 @@ pub fn main() -> i32 { } ``` -Linear values must be consumed along every path. `drop(x)` is a built-in sink that consumes the value. +Linear values must be consumed along every path. You can consume them with a terminal method (like `FileRead.close()` or `read_to_string()`), or with `drop(x)` as a last resort. ## 10) Borrow-lite: &T parameters diff --git a/capc/src/codegen/intrinsics.rs b/capc/src/codegen/intrinsics.rs index 5adc6ff..ef80c44 100644 --- a/capc/src/codegen/intrinsics.rs +++ b/capc/src/codegen/intrinsics.rs @@ -49,6 +49,46 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { params: vec![AbiType::Handle, AbiType::String, AbiType::ResultString], ret: AbiType::ResultString, }; + let fs_read_bytes = FnSig { + params: vec![AbiType::Handle, AbiType::String], + ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; + let fs_read_bytes_abi = FnSig { + params: vec![ + AbiType::Handle, + AbiType::String, + AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + ], + ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; + let fs_list_dir = FnSig { + params: vec![AbiType::Handle, AbiType::String], + ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; + let fs_list_dir_abi = FnSig { + params: vec![ + AbiType::Handle, + AbiType::String, + AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + ], + ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; + let fs_exists = FnSig { + params: vec![AbiType::Handle, AbiType::String], + ret: AbiType::Bool, + }; + let fs_readfs_close = FnSig { + params: vec![AbiType::Handle], + ret: AbiType::Unit, + }; + let fs_filesystem_close = FnSig { + params: vec![AbiType::Handle], + ret: AbiType::Unit, + }; + let fs_dir_close = FnSig { + params: vec![AbiType::Handle], + ret: AbiType::Unit, + }; let fs_file_read_to_string = FnSig { params: vec![AbiType::Handle], ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), @@ -57,6 +97,25 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { params: vec![AbiType::Handle, AbiType::ResultString], ret: AbiType::ResultString, }; + let fs_file_read_close = FnSig { + params: vec![AbiType::Handle], + ret: AbiType::Unit, + }; + let fs_dir_list_dir = FnSig { + params: vec![AbiType::Handle], + ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; + let fs_dir_list_dir_abi = FnSig { + params: vec![ + AbiType::Handle, + AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + ], + ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; + let fs_join = FnSig { + params: vec![AbiType::String, AbiType::String], + ret: AbiType::String, + }; // Console. let console_println = FnSig { params: vec![AbiType::Handle, AbiType::String], @@ -108,6 +167,48 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { params: vec![AbiType::Handle], ret: AbiType::Handle, }; + let system_mint_net = FnSig { + params: vec![AbiType::Handle], + ret: AbiType::Handle, + }; + // Net. + let net_connect = FnSig { + params: vec![AbiType::Handle, AbiType::String, AbiType::I32], + ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; + let net_connect_abi = FnSig { + params: vec![ + AbiType::Handle, + AbiType::String, + AbiType::I32, + AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + ], + ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; + let net_read_to_string = FnSig { + params: vec![AbiType::Handle], + ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), + }; + let net_read_to_string_abi = FnSig { + params: vec![AbiType::Handle, AbiType::ResultString], + ret: AbiType::ResultString, + }; + let net_write = FnSig { + params: vec![AbiType::Handle, AbiType::String], + ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + }; + let net_write_abi = FnSig { + params: vec![ + AbiType::Handle, + AbiType::String, + AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + ], + ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + }; + let net_close = FnSig { + params: vec![AbiType::Handle], + ret: AbiType::Unit, + }; let args_at = FnSig { params: vec![AbiType::Handle, AbiType::I32], ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), @@ -158,6 +259,22 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ], ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), }; + let mem_buffer_extend = FnSig { + params: vec![AbiType::Handle, AbiType::Handle], + ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + }; + let mem_buffer_extend_abi = FnSig { + params: vec![ + AbiType::Handle, + AbiType::Handle, + AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + ], + ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + }; + let mem_buffer_is_empty = FnSig { + params: vec![AbiType::Handle], + ret: AbiType::Bool, + }; let mem_buffer_as_slice = FnSig { params: vec![AbiType::Handle], ret: AbiType::Handle, @@ -211,6 +328,39 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ], ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), }; + let vec_u8_extend = FnSig { + params: vec![AbiType::Handle, AbiType::Handle], + ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + }; + let vec_u8_extend_abi = FnSig { + params: vec![ + AbiType::Handle, + AbiType::Handle, + AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + ], + ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + }; + let vec_u8_filter = FnSig { + params: vec![AbiType::Handle, AbiType::U8], + ret: AbiType::Handle, + }; + let vec_u8_map_add = FnSig { + params: vec![AbiType::Handle, AbiType::U8], + ret: AbiType::Handle, + }; + let vec_u8_slice = FnSig { + params: vec![AbiType::Handle, AbiType::I32, AbiType::I32], + ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; + let vec_u8_slice_abi = FnSig { + params: vec![ + AbiType::Handle, + AbiType::I32, + AbiType::I32, + AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + ], + ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; let vec_u8_pop = FnSig { params: vec![AbiType::Handle], ret: AbiType::Result(Box::new(AbiType::U8), Box::new(AbiType::I32)), @@ -267,6 +417,26 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ], ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), }; + let vec_i32_extend = FnSig { + params: vec![AbiType::Handle, AbiType::Handle], + ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + }; + let vec_i32_extend_abi = FnSig { + params: vec![ + AbiType::Handle, + AbiType::Handle, + AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + ], + ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + }; + let vec_i32_filter = FnSig { + params: vec![AbiType::Handle, AbiType::I32], + ret: AbiType::Handle, + }; + let vec_i32_map_add = FnSig { + params: vec![AbiType::Handle, AbiType::I32], + ret: AbiType::Handle, + }; let vec_i32_pop = FnSig { params: vec![AbiType::Handle], ret: AbiType::Result(Box::new(AbiType::I32), Box::new(AbiType::I32)), @@ -306,6 +476,18 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ], ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), }; + let vec_string_extend = FnSig { + params: vec![AbiType::Handle, AbiType::Handle], + ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + }; + let vec_string_extend_abi = FnSig { + params: vec![ + AbiType::Handle, + AbiType::Handle, + AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + ], + ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + }; let vec_string_pop = FnSig { params: vec![AbiType::Handle], ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), @@ -343,6 +525,18 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { params: vec![AbiType::String, AbiType::U8], ret: AbiType::Handle, }; + let string_trim = FnSig { + params: vec![AbiType::String], + ret: AbiType::String, + }; + let string_trim_start = FnSig { + params: vec![AbiType::String], + ret: AbiType::String, + }; + let string_trim_end = FnSig { + params: vec![AbiType::String], + ret: AbiType::String, + }; // Vec lengths. let vec_u8_len = FnSig { params: vec![AbiType::Handle], @@ -404,6 +598,16 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + map.insert( + "sys.system.RootCap__mint_net".to_string(), + FnInfo { + sig: system_mint_net, + abi_sig: None, + symbol: "capable_rt_mint_net".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); map.insert( "sys.args.Args__len".to_string(), FnInfo { @@ -444,6 +648,47 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + // === Net === + map.insert( + "sys.net.Net__connect".to_string(), + FnInfo { + sig: net_connect, + abi_sig: Some(net_connect_abi), + symbol: "capable_rt_net_connect".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.net.TcpConn__read_to_string".to_string(), + FnInfo { + sig: net_read_to_string, + abi_sig: Some(net_read_to_string_abi), + symbol: "capable_rt_net_read_to_string".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.net.TcpConn__write".to_string(), + FnInfo { + sig: net_write, + abi_sig: Some(net_write_abi), + symbol: "capable_rt_net_write".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.net.TcpConn__close".to_string(), + FnInfo { + sig: net_close, + abi_sig: None, + symbol: "capable_rt_net_close".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); // === Alloc === map.insert( "sys.system.RootCap__mint_alloc_default".to_string(), @@ -611,6 +856,46 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + map.insert( + "sys.fs.ReadFS__read_bytes".to_string(), + FnInfo { + sig: fs_read_bytes.clone(), + abi_sig: Some(fs_read_bytes_abi.clone()), + symbol: "capable_rt_fs_read_bytes".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.fs.ReadFS__list_dir".to_string(), + FnInfo { + sig: fs_list_dir, + abi_sig: Some(fs_list_dir_abi), + symbol: "capable_rt_fs_list_dir".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.fs.ReadFS__exists".to_string(), + FnInfo { + sig: fs_exists.clone(), + abi_sig: None, + symbol: "capable_rt_fs_exists".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.fs.ReadFS__close".to_string(), + FnInfo { + sig: fs_readfs_close, + abi_sig: None, + symbol: "capable_rt_fs_readfs_close".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); map.insert( "sys.fs.Filesystem__root_dir".to_string(), FnInfo { @@ -621,6 +906,16 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + map.insert( + "sys.fs.Filesystem__close".to_string(), + FnInfo { + sig: fs_filesystem_close, + abi_sig: None, + symbol: "capable_rt_fs_filesystem_close".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); map.insert( "sys.fs.Dir__subdir".to_string(), FnInfo { @@ -641,6 +936,46 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + map.insert( + "sys.fs.Dir__read_bytes".to_string(), + FnInfo { + sig: fs_read_bytes, + abi_sig: Some(fs_read_bytes_abi), + symbol: "capable_rt_fs_dir_read_bytes".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.fs.Dir__list_dir".to_string(), + FnInfo { + sig: fs_dir_list_dir, + abi_sig: Some(fs_dir_list_dir_abi), + symbol: "capable_rt_fs_dir_list_dir".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.fs.Dir__exists".to_string(), + FnInfo { + sig: fs_exists, + abi_sig: None, + symbol: "capable_rt_fs_dir_exists".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.fs.Dir__close".to_string(), + FnInfo { + sig: fs_dir_close, + abi_sig: None, + symbol: "capable_rt_fs_dir_close".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); map.insert( "sys.fs.FileRead__read_to_string".to_string(), FnInfo { @@ -651,6 +986,26 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + map.insert( + "sys.fs.FileRead__close".to_string(), + FnInfo { + sig: fs_file_read_close, + abi_sig: None, + symbol: "capable_rt_fs_file_read_close".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.fs.join".to_string(), + FnInfo { + sig: fs_join, + abi_sig: None, + symbol: "capable_rt_fs_join".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); // === Buffer + slices === map.insert( "sys.buffer.Alloc__buffer_new".to_string(), @@ -752,6 +1107,26 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + map.insert( + "sys.buffer.Buffer__extend".to_string(), + FnInfo { + sig: mem_buffer_extend, + abi_sig: Some(mem_buffer_extend_abi), + symbol: "capable_rt_buffer_extend".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.buffer.Buffer__is_empty".to_string(), + FnInfo { + sig: mem_buffer_is_empty, + abi_sig: None, + symbol: "capable_rt_buffer_is_empty".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); map.insert( "sys.buffer.Buffer__as_slice".to_string(), FnInfo { @@ -903,6 +1278,46 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + map.insert( + "sys.vec.VecU8__extend".to_string(), + FnInfo { + sig: vec_u8_extend, + abi_sig: Some(vec_u8_extend_abi), + symbol: "capable_rt_vec_u8_extend".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.vec.VecU8__filter".to_string(), + FnInfo { + sig: vec_u8_filter, + abi_sig: None, + symbol: "capable_rt_vec_u8_filter".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.vec.VecU8__map_add".to_string(), + FnInfo { + sig: vec_u8_map_add, + abi_sig: None, + symbol: "capable_rt_vec_u8_map_add".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.vec.VecU8__slice".to_string(), + FnInfo { + sig: vec_u8_slice, + abi_sig: Some(vec_u8_slice_abi), + symbol: "capable_rt_vec_u8_slice".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); map.insert( "sys.vec.VecU8__pop".to_string(), FnInfo { @@ -963,6 +1378,36 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + map.insert( + "sys.vec.VecI32__extend".to_string(), + FnInfo { + sig: vec_i32_extend, + abi_sig: Some(vec_i32_extend_abi), + symbol: "capable_rt_vec_i32_extend".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.vec.VecI32__filter".to_string(), + FnInfo { + sig: vec_i32_filter, + abi_sig: None, + symbol: "capable_rt_vec_i32_filter".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.vec.VecI32__map_add".to_string(), + FnInfo { + sig: vec_i32_map_add, + abi_sig: None, + symbol: "capable_rt_vec_i32_map_add".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); map.insert( "sys.vec.VecI32__pop".to_string(), FnInfo { @@ -1003,6 +1448,16 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + map.insert( + "sys.vec.VecString__extend".to_string(), + FnInfo { + sig: vec_string_extend, + abi_sig: Some(vec_string_extend_abi), + symbol: "capable_rt_vec_string_extend".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); map.insert( "sys.vec.VecString__pop".to_string(), FnInfo { @@ -1085,6 +1540,36 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + map.insert( + "sys.string.string__trim".to_string(), + FnInfo { + sig: string_trim, + abi_sig: None, + symbol: "capable_rt_string_trim".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.string.string__trim_start".to_string(), + FnInfo { + sig: string_trim_start, + abi_sig: None, + symbol: "capable_rt_string_trim_start".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); + map.insert( + "sys.string.string__trim_end".to_string(), + FnInfo { + sig: string_trim_end, + abi_sig: None, + symbol: "capable_rt_string_trim_end".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); // === Bytes === map.insert( "sys.bytes.u8__is_whitespace".to_string(), diff --git a/capc/tests/run.rs b/capc/tests/run.rs index 05cfc74..3dd4222 100644 --- a/capc/tests/run.rs +++ b/capc/tests/run.rs @@ -92,6 +92,20 @@ fn run_fs_attenuation() { ); } +#[test] +fn run_fs_helpers() { + let out_dir = make_out_dir("fs_helpers"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/fs_helpers.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("fs helpers ok"), "stdout was: {stdout:?}"); +} + #[test] fn run_match_expr() { let out_dir = make_out_dir("match_expr"); @@ -459,6 +473,20 @@ fn run_vec_helpers() { assert!(stdout.contains("vec ok"), "stdout was: {stdout:?}"); } +#[test] +fn run_net_helpers() { + let out_dir = make_out_dir("net_helpers"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/net_helpers.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("net err ok"), "stdout was: {stdout:?}"); +} + #[test] fn run_string_split() { let out_dir = make_out_dir("string_split"); diff --git a/capc/tests/typecheck.rs b/capc/tests/typecheck.rs index 76a3eff..d7d5756 100644 --- a/capc/tests/typecheck.rs +++ b/capc/tests/typecheck.rs @@ -36,6 +36,14 @@ fn typecheck_fs_read_ok() { type_check_program(&module, &stdlib, &[]).expect("typecheck module"); } +#[test] +fn typecheck_fs_close_ok() { + let source = load_program("should_pass_fs_close.cap"); + let module = parse_module(&source).expect("parse module"); + let stdlib = load_stdlib().expect("load stdlib"); + type_check_program(&module, &stdlib, &[]).expect("typecheck module"); +} + #[test] fn typecheck_struct_literal_ok() { let source = load_program("struct_literal.cap"); @@ -350,6 +358,14 @@ fn typecheck_linear_drop_ok() { type_check_program(&module, &stdlib, &[]).expect("typecheck module"); } +#[test] +fn typecheck_linear_close_ok() { + let source = load_program("should_pass_linear_close.cap"); + let module = parse_module(&source).expect("parse module"); + let stdlib = load_stdlib().expect("load stdlib"); + type_check_program(&module, &stdlib, &[]).expect("typecheck module"); +} + #[test] fn typecheck_linear_not_consumed_fails() { let source = load_program("should_fail_linear_not_consumed.cap"); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index c0f0674..319d5f2 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::ffi::OsStr; use std::io::{self, Read, Write}; +use std::net::TcpStream; use std::path::{Component, Path, PathBuf}; use std::sync::{LazyLock, Mutex}; @@ -17,6 +18,18 @@ static DIRS: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); static FILE_READS: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); +static ROOT_CAPS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); +static CONSOLES: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); +static ARGS_CAPS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); +static STDIN_CAPS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); +static NET_CAPS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); +static TCP_CONNS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); static SLICES: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); static BUFFERS: LazyLock>>> = @@ -51,6 +64,12 @@ struct FileReadState { rel: PathBuf, } +#[repr(C)] +pub struct RawString { + ptr: *const u8, + len: u64, +} + #[derive(Copy, Clone, Debug)] struct SliceState { ptr: usize, @@ -95,6 +114,14 @@ fn take_handle( table.remove(&handle) } +fn has_handle( + table: &LazyLock>>, + handle: Handle, + label: &'static str, +) -> bool { + with_table(table, label, |table| table.contains_key(&handle)) +} + fn with_table( table: &LazyLock>>, label: &'static str, @@ -104,19 +131,117 @@ fn with_table( f(&mut table) } +fn to_raw_string(value: String) -> RawString { + let bytes = value.into_bytes().into_boxed_slice(); + let len = bytes.len() as u64; + let ptr = Box::into_raw(bytes) as *const u8; + RawString { ptr, len } +} + +fn write_handle_result( + out_ok: *mut Handle, + out_err: *mut i32, + result: Result, +) -> u8 { + unsafe { + if !out_ok.is_null() { + *out_ok = 0; + } + if !out_err.is_null() { + *out_err = 0; + } + } + match result { + Ok(handle) => { + unsafe { + if !out_ok.is_null() { + *out_ok = handle; + } + } + 0 + } + Err(err) => { + unsafe { + if !out_err.is_null() { + *out_err = err as i32; + } + } + 1 + } + } +} + +fn write_handle_result_code( + out_ok: *mut Handle, + out_err: *mut i32, + result: Result, +) -> u8 { + unsafe { + if !out_ok.is_null() { + *out_ok = 0; + } + if !out_err.is_null() { + *out_err = 0; + } + } + match result { + Ok(handle) => { + unsafe { + if !out_ok.is_null() { + *out_ok = handle; + } + } + 0 + } + Err(err) => { + unsafe { + if !out_err.is_null() { + *out_err = err; + } + } + 1 + } + } +} + #[no_mangle] pub extern "C" fn capable_rt_mint_console(_sys: Handle) -> Handle { - new_handle() + if !has_handle(&ROOT_CAPS, _sys, "root cap table") { + return 0; + } + let handle = new_handle(); + insert_handle(&CONSOLES, handle, (), "console table"); + handle } #[no_mangle] pub extern "C" fn capable_rt_mint_args(_sys: Handle) -> Handle { - new_handle() + if !has_handle(&ROOT_CAPS, _sys, "root cap table") { + return 0; + } + let handle = new_handle(); + insert_handle(&ARGS_CAPS, handle, (), "args table"); + handle } #[no_mangle] pub extern "C" fn capable_rt_mint_stdin(_sys: Handle) -> Handle { - new_handle() + if !has_handle(&ROOT_CAPS, _sys, "root cap table") { + return 0; + } + let handle = new_handle(); + insert_handle(&STDIN_CAPS, handle, (), "stdin table"); + handle +} + +#[no_mangle] +pub extern "C" fn capable_rt_mint_net(_sys: Handle) -> Handle { + if !has_handle(&ROOT_CAPS, _sys, "root cap table") { + return 0; + } + let handle = new_handle(); + insert_handle(&NET_CAPS, handle, (), "net table"); + handle } #[no_mangle] @@ -125,6 +250,9 @@ pub extern "C" fn capable_rt_mint_readfs( root_ptr: *const u8, root_len: usize, ) -> Handle { + if !has_handle(&ROOT_CAPS, _sys, "root cap table") { + return 0; + } let root = unsafe { read_str(root_ptr, root_len) }; let root_path = match root { Some(path) => normalize_root(Path::new(&path)), @@ -144,6 +272,9 @@ pub extern "C" fn capable_rt_mint_filesystem( root_ptr: *const u8, root_len: usize, ) -> Handle { + if !has_handle(&ROOT_CAPS, _sys, "root cap table") { + return 0; + } let root = unsafe { read_str(root_ptr, root_len) }; let root_path = match root { Some(path) => normalize_root(Path::new(&path)), @@ -181,6 +312,11 @@ pub extern "C" fn capable_rt_fs_root_dir(fs: Handle) -> Handle { handle } +#[no_mangle] +pub extern "C" fn capable_rt_fs_filesystem_close(fs: Handle) { + take_handle(&FILESYSTEMS, fs, "filesystem table"); +} + #[no_mangle] pub extern "C" fn capable_rt_fs_subdir( dir: Handle, @@ -243,8 +379,209 @@ pub extern "C" fn capable_rt_fs_open_read( handle } +#[no_mangle] +pub extern "C" fn capable_rt_fs_dir_close(dir: Handle) { + take_handle(&DIRS, dir, "dir table"); +} + +#[no_mangle] +pub extern "C" fn capable_rt_fs_exists( + fs: Handle, + path_ptr: *const u8, + path_len: usize, +) -> u8 { + let path = unsafe { read_str(path_ptr, path_len) }; + let state = take_handle(&READ_FS, fs, "readfs table"); + let (Some(state), Some(path)) = (state, path) else { + return 0; + }; + let Some(relative) = normalize_relative(Path::new(&path)) else { + return 0; + }; + let full = state.root.join(relative); + match full.canonicalize() { + Ok(path) => u8::from(path.starts_with(&state.root) && path.exists()), + Err(_) => 0, + } +} + +#[no_mangle] +pub extern "C" fn capable_rt_fs_read_bytes( + fs: Handle, + path_ptr: *const u8, + path_len: usize, + out_ok: *mut Handle, + out_err: *mut i32, +) -> u8 { + let path = unsafe { read_str(path_ptr, path_len) }; + let state = take_handle(&READ_FS, fs, "readfs table"); + let (Some(state), Some(path)) = (state, path) else { + return write_handle_result(out_ok, out_err, Err(FsErr::PermissionDenied)); + }; + let Some(relative) = normalize_relative(Path::new(&path)) else { + return write_handle_result(out_ok, out_err, Err(FsErr::InvalidPath)); + }; + let full = match resolve_rooted_path(&state.root, &relative) { + Ok(path) => path, + Err(err) => return write_handle_result(out_ok, out_err, Err(err)), + }; + match std::fs::read(&full) { + Ok(bytes) => { + let handle = new_handle(); + with_table(&VECS_U8, "vec u8 table", |table| { + table.insert(handle, bytes); + }); + write_handle_result(out_ok, out_err, Ok(handle)) + } + Err(err) => write_handle_result(out_ok, out_err, Err(map_fs_err(err))), + } +} + +#[no_mangle] +pub extern "C" fn capable_rt_fs_list_dir( + fs: Handle, + path_ptr: *const u8, + path_len: usize, + out_ok: *mut Handle, + out_err: *mut i32, +) -> u8 { + let path = unsafe { read_str(path_ptr, path_len) }; + let state = take_handle(&READ_FS, fs, "readfs table"); + let (Some(state), Some(path)) = (state, path) else { + return write_handle_result(out_ok, out_err, Err(FsErr::PermissionDenied)); + }; + let Some(relative) = normalize_relative(Path::new(&path)) else { + return write_handle_result(out_ok, out_err, Err(FsErr::InvalidPath)); + }; + let full = match resolve_rooted_path(&state.root, &relative) { + Ok(path) => path, + Err(err) => return write_handle_result(out_ok, out_err, Err(err)), + }; + let entries = match std::fs::read_dir(&full) { + Ok(entries) => entries, + Err(err) => return write_handle_result(out_ok, out_err, Err(map_fs_err(err))), + }; + let mut names = Vec::new(); + for entry in entries { + let entry = match entry { + Ok(entry) => entry, + Err(err) => return write_handle_result(out_ok, out_err, Err(map_fs_err(err))), + }; + names.push(entry.file_name().to_string_lossy().to_string()); + } + let handle = new_handle(); + with_table(&VECS_STRING, "vec string table", |table| { + table.insert(handle, names); + }); + write_handle_result(out_ok, out_err, Ok(handle)) +} + +#[no_mangle] +pub extern "C" fn capable_rt_fs_dir_exists( + dir: Handle, + name_ptr: *const u8, + name_len: usize, +) -> u8 { + let name = unsafe { read_str(name_ptr, name_len) }; + let state = take_handle(&DIRS, dir, "dir table"); + let (Some(state), Some(name)) = (state, name) else { + return 0; + }; + let Some(name_rel) = normalize_relative(Path::new(&name)) else { + return 0; + }; + let combined = state.rel.join(name_rel); + match resolve_rooted_path(&state.root, &combined) { + Ok(path) => u8::from(path.exists()), + Err(_) => 0, + } +} + +#[no_mangle] +pub extern "C" fn capable_rt_fs_dir_read_bytes( + dir: Handle, + name_ptr: *const u8, + name_len: usize, + out_ok: *mut Handle, + out_err: *mut i32, +) -> u8 { + let name = unsafe { read_str(name_ptr, name_len) }; + let state = take_handle(&DIRS, dir, "dir table"); + let (Some(state), Some(name)) = (state, name) else { + return write_handle_result(out_ok, out_err, Err(FsErr::PermissionDenied)); + }; + let Some(name_rel) = normalize_relative(Path::new(&name)) else { + return write_handle_result(out_ok, out_err, Err(FsErr::InvalidPath)); + }; + let combined = state.rel.join(name_rel); + let full = match resolve_rooted_path(&state.root, &combined) { + Ok(path) => path, + Err(err) => return write_handle_result(out_ok, out_err, Err(err)), + }; + match std::fs::read(&full) { + Ok(bytes) => { + let handle = new_handle(); + with_table(&VECS_U8, "vec u8 table", |table| { + table.insert(handle, bytes); + }); + write_handle_result(out_ok, out_err, Ok(handle)) + } + Err(err) => write_handle_result(out_ok, out_err, Err(map_fs_err(err))), + } +} + +#[no_mangle] +pub extern "C" fn capable_rt_fs_dir_list_dir( + dir: Handle, + out_ok: *mut Handle, + out_err: *mut i32, +) -> u8 { + let state = take_handle(&DIRS, dir, "dir table"); + let Some(state) = state else { + return write_handle_result(out_ok, out_err, Err(FsErr::PermissionDenied)); + }; + let full = match resolve_rooted_path(&state.root, &state.rel) { + Ok(path) => path, + Err(err) => return write_handle_result(out_ok, out_err, Err(err)), + }; + let entries = match std::fs::read_dir(&full) { + Ok(entries) => entries, + Err(err) => return write_handle_result(out_ok, out_err, Err(map_fs_err(err))), + }; + let mut names = Vec::new(); + for entry in entries { + let entry = match entry { + Ok(entry) => entry, + Err(err) => return write_handle_result(out_ok, out_err, Err(map_fs_err(err))), + }; + names.push(entry.file_name().to_string_lossy().to_string()); + } + let handle = new_handle(); + with_table(&VECS_STRING, "vec string table", |table| { + table.insert(handle, names); + }); + write_handle_result(out_ok, out_err, Ok(handle)) +} + +#[no_mangle] +pub extern "C" fn capable_rt_fs_join( + a_ptr: *const u8, + a_len: usize, + b_ptr: *const u8, + b_len: usize, +) -> RawString { + let (Some(a), Some(b)) = (unsafe { read_str(a_ptr, a_len) }, unsafe { read_str(b_ptr, b_len) }) else { + return RawString { ptr: std::ptr::null(), len: 0 }; + }; + let joined = Path::new(&a).join(&b); + to_raw_string(joined.to_string_lossy().to_string()) +} + #[no_mangle] pub extern "C" fn capable_rt_assert(_sys: Handle, cond: u8) { + if !has_handle(&CONSOLES, _sys, "console table") { + return; + } if cond == 0 { eprintln!("assertion failed"); std::process::exit(1); @@ -253,16 +590,25 @@ pub extern "C" fn capable_rt_assert(_sys: Handle, cond: u8) { #[no_mangle] pub extern "C" fn capable_rt_console_print(_console: Handle, ptr: *const u8, len: usize) { + if !has_handle(&CONSOLES, _console, "console table") { + return; + } unsafe { write_bytes(ptr, len, false) }; } #[no_mangle] pub extern "C" fn capable_rt_console_println(_console: Handle, ptr: *const u8, len: usize) { + if !has_handle(&CONSOLES, _console, "console table") { + return; + } unsafe { write_bytes(ptr, len, true) }; } #[no_mangle] pub extern "C" fn capable_rt_console_print_i32(_console: Handle, value: i32) { + if !has_handle(&CONSOLES, _console, "console table") { + return; + } let mut stdout = io::stdout().lock(); let _ = write!(stdout, "{value}"); let _ = stdout.flush(); @@ -270,6 +616,9 @@ pub extern "C" fn capable_rt_console_print_i32(_console: Handle, value: i32) { #[no_mangle] pub extern "C" fn capable_rt_console_println_i32(_console: Handle, value: i32) { + if !has_handle(&CONSOLES, _console, "console table") { + return; + } let mut stdout = io::stdout().lock(); let _ = writeln!(stdout, "{value}"); let _ = stdout.flush(); @@ -357,6 +706,11 @@ pub extern "C" fn capable_rt_fs_read_to_string( } } +#[no_mangle] +pub extern "C" fn capable_rt_fs_readfs_close(fs: Handle) { + take_handle(&READ_FS, fs, "readfs table"); +} + #[no_mangle] pub extern "C" fn capable_rt_fs_file_read_to_string( file: Handle, @@ -384,9 +738,116 @@ pub extern "C" fn capable_rt_fs_file_read_to_string( } } +#[no_mangle] +pub extern "C" fn capable_rt_fs_file_read_close(file: Handle) { + take_handle(&FILE_READS, file, "file read table"); +} + +#[no_mangle] +pub extern "C" fn capable_rt_net_connect( + net: Handle, + host_ptr: *const u8, + host_len: usize, + port: i32, + out_ok: *mut Handle, + out_err: *mut i32, +) -> u8 { + if !has_handle(&NET_CAPS, net, "net table") { + return write_handle_result_code(out_ok, out_err, Err(NetErr::IoError as i32)); + } + let host = unsafe { read_str(host_ptr, host_len) }; + let Some(host) = host else { + return write_handle_result_code(out_ok, out_err, Err(NetErr::InvalidAddress as i32)); + }; + if host.is_empty() || port <= 0 || port > u16::MAX as i32 { + return write_handle_result_code(out_ok, out_err, Err(NetErr::InvalidAddress as i32)); + } + match TcpStream::connect((host.as_str(), port as u16)) { + Ok(stream) => { + let handle = new_handle(); + insert_handle(&TCP_CONNS, handle, stream, "tcp conn table"); + write_handle_result_code(out_ok, out_err, Ok(handle)) + } + Err(err) => write_handle_result_code(out_ok, out_err, Err(map_net_err(err) as i32)), + } +} + +#[no_mangle] +pub extern "C" fn capable_rt_net_read_to_string( + conn: Handle, + out_ptr: *mut *const u8, + out_len: *mut u64, + out_err: *mut i32, +) -> u8 { + let result = with_table(&TCP_CONNS, "tcp conn table", |table| { + let Some(stream) = table.get_mut(&conn) else { + return Err(NetErr::IoError); + }; + let mut buffer = String::new(); + match stream.read_to_string(&mut buffer) { + Ok(_) => Ok(buffer), + Err(err) => Err(map_net_err(err)), + } + }); + write_string_result( + out_ptr, + out_len, + out_err, + result.map_err(|err| err as i32), + ) +} + +#[no_mangle] +pub extern "C" fn capable_rt_net_write( + conn: Handle, + data_ptr: *const u8, + data_len: usize, + out_err: *mut i32, +) -> u8 { + let data = unsafe { read_str(data_ptr, data_len) }; + let Some(data) = data else { + unsafe { + if !out_err.is_null() { + *out_err = NetErr::InvalidData as i32; + } + } + return 1; + }; + with_table(&TCP_CONNS, "tcp conn table", |table| { + if !out_err.is_null() { + unsafe { + *out_err = 0; + } + } + let Some(stream) = table.get_mut(&conn) else { + unsafe { + if !out_err.is_null() { + *out_err = NetErr::IoError as i32; + } + } + return 1; + }; + if let Err(err) = stream.write_all(data.as_bytes()) { + unsafe { + if !out_err.is_null() { + *out_err = map_net_err(err) as i32; + } + } + return 1; + } + 0 + }) +} + +#[no_mangle] +pub extern "C" fn capable_rt_net_close(conn: Handle) { + take_handle(&TCP_CONNS, conn, "tcp conn table"); +} + #[no_mangle] pub extern "C" fn capable_rt_start() -> i32 { let sys = new_handle(); + insert_handle(&ROOT_CAPS, sys, (), "root cap table"); unsafe { capable_main(sys) } } @@ -535,26 +996,64 @@ pub extern "C" fn capable_rt_buffer_new( if !out_ok.is_null() { *out_ok = handle; } - } - 0 -} - -#[no_mangle] -pub extern "C" fn capable_rt_buffer_len(buffer: Handle) -> i32 { - with_table(&BUFFERS, "buffer table", |table| { - table - .get(&buffer) - .map(|data| data.len().min(i32::MAX as usize) as i32) - .unwrap_or(0) + } + 0 +} + +#[no_mangle] +pub extern "C" fn capable_rt_buffer_len(buffer: Handle) -> i32 { + with_table(&BUFFERS, "buffer table", |table| { + table + .get(&buffer) + .map(|data| data.len().min(i32::MAX as usize) as i32) + .unwrap_or(0) + }) +} + +#[no_mangle] +pub extern "C" fn capable_rt_buffer_push( + buffer: Handle, + value: u8, + out_err: *mut i32, +) -> u8 { + with_table(&BUFFERS, "buffer table", |table| { + let Some(data) = table.get_mut(&buffer) else { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + }; + if data.try_reserve(1).is_err() { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + } + data.push(value); + 0 }) } #[no_mangle] -pub extern "C" fn capable_rt_buffer_push( +pub extern "C" fn capable_rt_buffer_extend( buffer: Handle, - value: u8, + slice: Handle, out_err: *mut i32, ) -> u8 { + let slice_state = with_table(&SLICES, "slice table", |table| table.get(&slice).copied()); + let Some(slice_state) = slice_state else { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + }; + let bytes = unsafe { std::slice::from_raw_parts(slice_state.ptr as *const u8, slice_state.len) }; with_table(&BUFFERS, "buffer table", |table| { let Some(data) = table.get_mut(&buffer) else { unsafe { @@ -564,7 +1063,7 @@ pub extern "C" fn capable_rt_buffer_push( } return 1; }; - if data.try_reserve(1).is_err() { + if data.try_reserve(bytes.len()).is_err() { unsafe { if !out_err.is_null() { *out_err = 0; @@ -572,11 +1071,18 @@ pub extern "C" fn capable_rt_buffer_push( } return 1; } - data.push(value); + data.extend_from_slice(bytes); 0 }) } +#[no_mangle] +pub extern "C" fn capable_rt_buffer_is_empty(buffer: Handle) -> u8 { + with_table(&BUFFERS, "buffer table", |table| { + u8::from(table.get(&buffer).map(|data| data.is_empty()).unwrap_or(true)) + }) +} + #[no_mangle] pub extern "C" fn capable_rt_buffer_free(_alloc: Handle, buffer: Handle) { with_table(&BUFFERS, "buffer table", |table| { @@ -751,6 +1257,136 @@ pub extern "C" fn capable_rt_vec_u8_push( }) } +#[no_mangle] +pub extern "C" fn capable_rt_vec_u8_extend( + vec: Handle, + other: Handle, + out_err: *mut i32, +) -> u8 { + with_table(&VECS_U8, "vec u8 table", |table| { + let Some(other_data) = table.get(&other).cloned() else { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + }; + let Some(data) = table.get_mut(&vec) else { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + }; + if data.try_reserve(other_data.len()).is_err() { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + } + data.extend_from_slice(&other_data); + 0 + }) +} + +#[no_mangle] +pub extern "C" fn capable_rt_vec_u8_filter(vec: Handle, value: u8) -> Handle { + let filtered = with_table(&VECS_U8, "vec u8 table", |table| { + table + .get(&vec) + .map(|data| data.iter().copied().filter(|b| *b == value).collect::>()) + }); + let Some(filtered) = filtered else { + return 0; + }; + let handle = new_handle(); + with_table(&VECS_U8, "vec u8 table", |table| { + table.insert(handle, filtered); + }); + handle +} + +#[no_mangle] +pub extern "C" fn capable_rt_vec_u8_map_add(vec: Handle, delta: u8) -> Handle { + let mapped = with_table(&VECS_U8, "vec u8 table", |table| { + table + .get(&vec) + .map(|data| data.iter().map(|b| b.wrapping_add(delta)).collect::>()) + }); + let Some(mapped) = mapped else { + return 0; + }; + let handle = new_handle(); + with_table(&VECS_U8, "vec u8 table", |table| { + table.insert(handle, mapped); + }); + handle +} + +#[no_mangle] +pub extern "C" fn capable_rt_vec_u8_slice( + vec: Handle, + start: i32, + len: i32, + out_ok: *mut Handle, + out_err: *mut i32, +) -> u8 { + let start = match usize::try_from(start) { + Ok(start) => start, + Err(_) => { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::OutOfRange as i32; + } + } + return 1; + } + }; + let len = match usize::try_from(len) { + Ok(len) => len, + Err(_) => { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::OutOfRange as i32; + } + } + return 1; + } + }; + let slice_state = with_table(&VECS_U8, "vec u8 table", |table| { + let Some(data) = table.get(&vec) else { + return None; + }; + if start > data.len() || start.saturating_add(len) > data.len() { + return None; + } + let ptr = if len == 0 { 0 } else { unsafe { data.as_ptr().add(start) as usize } }; + Some(SliceState { ptr, len }) + }); + let Some(slice_state) = slice_state else { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::OutOfRange as i32; + } + } + return 1; + }; + let handle = new_handle(); + with_table(&SLICES, "slice table", |table| { + table.insert(handle, slice_state); + }); + unsafe { + if !out_ok.is_null() { + *out_ok = handle; + } + } + 0 +} + #[no_mangle] pub extern "C" fn capable_rt_vec_u8_pop( vec: Handle, @@ -939,6 +1575,76 @@ pub extern "C" fn capable_rt_vec_i32_push( }) } +#[no_mangle] +pub extern "C" fn capable_rt_vec_i32_extend( + vec: Handle, + other: Handle, + out_err: *mut i32, +) -> u8 { + with_table(&VECS_I32, "vec i32 table", |table| { + let Some(other_data) = table.get(&other).cloned() else { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + }; + let Some(data) = table.get_mut(&vec) else { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + }; + if data.try_reserve(other_data.len()).is_err() { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + } + data.extend_from_slice(&other_data); + 0 + }) +} + +#[no_mangle] +pub extern "C" fn capable_rt_vec_i32_filter(vec: Handle, value: i32) -> Handle { + let filtered = with_table(&VECS_I32, "vec i32 table", |table| { + table + .get(&vec) + .map(|data| data.iter().copied().filter(|b| *b == value).collect::>()) + }); + let Some(filtered) = filtered else { + return 0; + }; + let handle = new_handle(); + with_table(&VECS_I32, "vec i32 table", |table| { + table.insert(handle, filtered); + }); + handle +} + +#[no_mangle] +pub extern "C" fn capable_rt_vec_i32_map_add(vec: Handle, delta: i32) -> Handle { + let mapped = with_table(&VECS_I32, "vec i32 table", |table| { + table + .get(&vec) + .map(|data| data.iter().map(|b| b.wrapping_add(delta)).collect::>()) + }); + let Some(mapped) = mapped else { + return 0; + }; + let handle = new_handle(); + with_table(&VECS_I32, "vec i32 table", |table| { + table.insert(handle, mapped); + }); + handle +} + #[no_mangle] pub extern "C" fn capable_rt_vec_i32_pop( vec: Handle, @@ -1060,6 +1766,42 @@ pub extern "C" fn capable_rt_vec_string_push( }) } +#[no_mangle] +pub extern "C" fn capable_rt_vec_string_extend( + vec: Handle, + other: Handle, + out_err: *mut i32, +) -> u8 { + with_table(&VECS_STRING, "vec string table", |table| { + let Some(other_data) = table.get(&other).cloned() else { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + }; + let Some(data) = table.get_mut(&vec) else { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + }; + if data.try_reserve(other_data.len()).is_err() { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + } + data.extend(other_data); + 0 + }) +} + #[no_mangle] pub extern "C" fn capable_rt_vec_string_pop( vec: Handle, @@ -1147,6 +1889,27 @@ pub extern "C" fn capable_rt_string_split_lines(ptr: *const u8, len: usize) -> H handle } +#[no_mangle] +pub extern "C" fn capable_rt_string_trim(ptr: *const u8, len: usize) -> RawString { + let value = unsafe { read_str(ptr, len) }; + let trimmed = value.as_deref().unwrap_or("").trim().to_string(); + to_raw_string(trimmed) +} + +#[no_mangle] +pub extern "C" fn capable_rt_string_trim_start(ptr: *const u8, len: usize) -> RawString { + let value = unsafe { read_str(ptr, len) }; + let trimmed = value.as_deref().unwrap_or("").trim_start().to_string(); + to_raw_string(trimmed) +} + +#[no_mangle] +pub extern "C" fn capable_rt_string_trim_end(ptr: *const u8, len: usize) -> RawString { + let value = unsafe { read_str(ptr, len) }; + let trimmed = value.as_deref().unwrap_or("").trim_end().to_string(); + to_raw_string(trimmed) +} + #[no_mangle] pub extern "C" fn capable_rt_string_starts_with( ptr: *const u8, @@ -1164,6 +1927,9 @@ pub extern "C" fn capable_rt_string_starts_with( #[no_mangle] pub extern "C" fn capable_rt_args_len(_sys: Handle) -> i32 { + if !has_handle(&ARGS_CAPS, _sys, "args table") { + return 0; + } ARGS.len().min(i32::MAX as usize) as i32 } @@ -1175,6 +1941,14 @@ pub extern "C" fn capable_rt_args_at( out_len: *mut u64, out_err: *mut i32, ) -> u8 { + if !has_handle(&ARGS_CAPS, _sys, "args table") { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + } let idx = match usize::try_from(index) { Ok(idx) => idx, Err(_) => { @@ -1212,6 +1986,9 @@ pub extern "C" fn capable_rt_read_stdin_to_string( out_len: *mut u64, out_err: *mut i32, ) -> u8 { + if !has_handle(&STDIN_CAPS, _sys, "stdin table") { + return write_string_result(out_ptr, out_len, out_err, Err(0)); + } let mut input = String::new(); let result = io::stdin().read_to_string(&mut input); match result { @@ -1286,6 +2063,13 @@ enum FsErr { IoError = 3, } +#[derive(Copy, Clone, Debug)] +enum NetErr { + InvalidAddress = 0, + IoError = 1, + InvalidData = 2, +} + fn map_fs_err(err: std::io::Error) -> FsErr { use std::io::ErrorKind; match err.kind() { @@ -1296,6 +2080,26 @@ fn map_fs_err(err: std::io::Error) -> FsErr { } } +fn map_net_err(err: std::io::Error) -> NetErr { + use std::io::ErrorKind; + match err.kind() { + ErrorKind::InvalidInput | ErrorKind::AddrNotAvailable | ErrorKind::AddrInUse => { + NetErr::InvalidAddress + } + ErrorKind::InvalidData => NetErr::InvalidData, + _ => NetErr::IoError, + } +} + +fn resolve_rooted_path(root: &Path, rel: &Path) -> Result { + let full = root.join(rel); + let full = full.canonicalize().map_err(map_fs_err)?; + if !full.starts_with(root) { + return Err(FsErr::InvalidPath); + } + Ok(full) +} + fn write_result( out_ptr: *mut *const u8, out_len: *mut u64, diff --git a/stdlib/sys/buffer.cap b/stdlib/sys/buffer.cap index 4f1bad6..3e07b6c 100644 --- a/stdlib/sys/buffer.cap +++ b/stdlib/sys/buffer.cap @@ -78,6 +78,14 @@ impl Buffer { return Err(AllocErr::Oom) } + pub fn extend(self, data: Slice[u8]) -> Result[unit, AllocErr] { + return Err(AllocErr::Oom) + } + + pub fn is_empty(self) -> bool { + return false + } + pub fn as_slice(self) -> Slice[u8] { return () } diff --git a/stdlib/sys/fs.cap b/stdlib/sys/fs.cap index 69bebcf..0ecf095 100644 --- a/stdlib/sys/fs.cap +++ b/stdlib/sys/fs.cap @@ -1,6 +1,8 @@ package unsafe module sys::fs +use sys::vec + pub capability struct ReadFS pub capability struct Filesystem pub capability struct Dir @@ -12,12 +14,32 @@ impl ReadFS { pub fn read_to_string(self, path: string) -> Result[string, FsErr] { return () } + + pub fn read_bytes(self, path: string) -> Result[vec::VecU8, FsErr] { + return Err(FsErr::IoError) + } + + pub fn list_dir(self, path: string) -> Result[vec::VecString, FsErr] { + return Err(FsErr::IoError) + } + + pub fn exists(self, path: string) -> bool { + return false + } + + pub fn close(self) -> unit { + return () + } } impl Filesystem { pub fn root_dir(self) -> Dir { return () } + + pub fn close(self) -> unit { + return () + } } impl Dir { @@ -29,14 +51,38 @@ impl Dir { return () } + pub fn read_bytes(self, name: string) -> Result[vec::VecU8, FsErr] { + return Err(FsErr::IoError) + } + + pub fn list_dir(self) -> Result[vec::VecString, FsErr] { + return Err(FsErr::IoError) + } + + pub fn exists(self, name: string) -> bool { + return false + } + pub fn read_to_string(self, name: string) -> Result[string, FsErr] { let file = self.open_read(name) return file.read_to_string() } + + pub fn close(self) -> unit { + return () + } } impl FileRead { pub fn read_to_string(self) -> Result[string, FsErr] { return () } + + pub fn close(self) -> unit { + return () + } +} + +pub fn join(a: string, b: string) -> string { + return "" } diff --git a/stdlib/sys/net.cap b/stdlib/sys/net.cap new file mode 100644 index 0000000..a34f95f --- /dev/null +++ b/stdlib/sys/net.cap @@ -0,0 +1,31 @@ +package unsafe +module sys::net + +pub copy capability struct Net +pub linear capability struct TcpConn + +pub enum NetErr { + InvalidAddress, + IoError, + InvalidData +} + +impl Net { + pub fn connect(self, host: string, port: i32) -> Result[TcpConn, NetErr] { + return Err(NetErr::IoError) + } +} + +impl TcpConn { + pub fn read_to_string(self: &TcpConn) -> Result[string, NetErr] { + return Err(NetErr::IoError) + } + + pub fn write(self: &TcpConn, data: string) -> Result[unit, NetErr] { + return Err(NetErr::IoError) + } + + pub fn close(self) -> unit { + return () + } +} diff --git a/stdlib/sys/string.cap b/stdlib/sys/string.cap index 862f865..3b3c99e 100644 --- a/stdlib/sys/string.cap +++ b/stdlib/sys/string.cap @@ -41,6 +41,26 @@ impl string { return () } + /// Intrinsic; implemented by the runtime. + pub fn trim(self) -> string { + return "" + } + + /// Intrinsic; implemented by the runtime. + pub fn trim_start(self) -> string { + return "" + } + + /// Intrinsic; implemented by the runtime. + pub fn trim_end(self) -> string { + return "" + } + + /// split_lines() is an alias for lines(). + pub fn split_lines(self) -> VecString { + return self.lines() + } + pub fn starts_with(self, prefix: string) -> bool { let self_len = self.len() let prefix_len = prefix.len() @@ -56,4 +76,36 @@ impl string { } return true } + + pub fn ends_with(self, suffix: string) -> bool { + let self_len = self.len() + let suffix_len = suffix.len() + if (suffix_len > self_len) { + return false + } + let i = 0 + while (i < suffix_len) { + let idx = self_len - suffix_len + i + if self.byte_at(idx) != suffix.byte_at(i) { + return false + } + i = i + 1 + } + return true + } + + pub fn starts_with_byte(self, prefix: u8) -> bool { + if (self.len() == 0) { + return false + } + return self.byte_at(0) == prefix + } + + pub fn ends_with_byte(self, suffix: u8) -> bool { + let len = self.len() + if (len == 0) { + return false + } + return self.byte_at(len - 1) == suffix + } } diff --git a/stdlib/sys/system.cap b/stdlib/sys/system.cap index 10c1551..ac53258 100644 --- a/stdlib/sys/system.cap +++ b/stdlib/sys/system.cap @@ -5,6 +5,7 @@ use sys::fs use sys::args use sys::stdin use sys::buffer +use sys::net pub copy capability struct RootCap @@ -32,4 +33,8 @@ impl RootCap { pub fn mint_stdin(self) -> stdin::Stdin { return () } + + pub fn mint_net(self) -> net::Net { + return () + } } diff --git a/stdlib/sys/vec.cap b/stdlib/sys/vec.cap index 1e4a76f..71c15c6 100644 --- a/stdlib/sys/vec.cap +++ b/stdlib/sys/vec.cap @@ -29,6 +29,22 @@ impl VecU8 { return Err(buffer::AllocErr::Oom) } + pub fn extend(self, other: VecU8) -> Result[unit, buffer::AllocErr] { + return Err(buffer::AllocErr::Oom) + } + + pub fn filter(self, value: u8) -> VecU8 { + return () + } + + pub fn map_add(self, delta: u8) -> VecU8 { + return () + } + + pub fn slice(self, start: i32, len: i32) -> Result[Slice[u8], VecErr] { + return Err(VecErr::OutOfRange) + } + pub fn pop(self) -> Result[u8, VecErr] { return Err(VecErr::Empty) } @@ -55,6 +71,18 @@ impl VecI32 { return Err(buffer::AllocErr::Oom) } + pub fn extend(self, other: VecI32) -> Result[unit, buffer::AllocErr] { + return Err(buffer::AllocErr::Oom) + } + + pub fn filter(self, value: i32) -> VecI32 { + return () + } + + pub fn map_add(self, delta: i32) -> VecI32 { + return () + } + pub fn pop(self) -> Result[i32, VecErr] { return Err(VecErr::Empty) } @@ -73,6 +101,10 @@ impl VecString { return Err(buffer::AllocErr::Oom) } + pub fn extend(self, other: VecString) -> Result[unit, buffer::AllocErr] { + return Err(buffer::AllocErr::Oom) + } + pub fn pop(self) -> Result[string, VecErr] { return Err(VecErr::Empty) } diff --git a/tests/programs/buffer_push_safe.cap b/tests/programs/buffer_push_safe.cap index 2a74fe6..5c295fb 100644 --- a/tests/programs/buffer_push_safe.cap +++ b/tests/programs/buffer_push_safe.cap @@ -15,13 +15,35 @@ pub fn main(rc: RootCap) -> i32 { return 1 } } + let v = alloc.vec_u8_new() + match v.push(9u8) { + Ok(u) => { u } + Err(e) => { + c.println("vec err") + alloc.vec_u8_free(v) + alloc.buffer_free(b) + return 1 + } + } + let slice = v.as_slice() + match b.extend(slice) { + Ok(u) => { u } + Err(e) => { + c.println("extend err") + alloc.vec_u8_free(v) + alloc.buffer_free(b) + return 1 + } + } let len = b.len() - c.assert(len == 2) - if (len == 2) { + c.assert(len == 3) + c.assert(!b.is_empty()) + if (len == 3) { c.println("push ok") } else { c.println("push bad") } + alloc.vec_u8_free(v) alloc.buffer_free(b) return 0 } diff --git a/tests/programs/fs_helpers.cap b/tests/programs/fs_helpers.cap new file mode 100644 index 0000000..7869340 --- /dev/null +++ b/tests/programs/fs_helpers.cap @@ -0,0 +1,50 @@ +package safe +module fs_helpers + +use sys::system +use sys::fs +use sys::buffer + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let alloc = rc.mint_alloc_default() + + let rfs = rc.mint_readfs("./config") + if !rfs.exists("app.txt") { + c.println("missing") + return 1 + } + + let rfs2 = rc.mint_readfs("./config") + match rfs2.read_bytes("app.txt") { + Ok(bytes) => { + c.assert(bytes.len() > 0) + alloc.vec_u8_free(bytes) + } + Err(_) => { + c.println("read_bytes failed") + return 1 + } + } + + let rfs3 = rc.mint_readfs("./config") + match rfs3.list_dir(".") { + Ok(entries) => { + c.assert(entries.len() > 0) + alloc.vec_string_free(entries) + } + Err(_) => { + c.println("list_dir failed") + return 1 + } + } + + let joined = fs::join("config", "app.txt") + if !joined.starts_with("config") { + c.println("join failed") + return 1 + } + + c.println("fs helpers ok") + return 0 +} diff --git a/tests/programs/net_helpers.cap b/tests/programs/net_helpers.cap new file mode 100644 index 0000000..c18db7c --- /dev/null +++ b/tests/programs/net_helpers.cap @@ -0,0 +1,20 @@ +package safe +module net_helpers +use sys::console +use sys::net +use sys::system + +pub fn main(sys: system::RootCap) -> i32 { + let c = sys.mint_console() + let net = sys.mint_net() + match (net.connect("", 80)) { + Ok(conn) => { + conn.close() + c.println("net ok") + } + Err(_) => { + c.println("net err ok") + } + } + return 0 +} diff --git a/tests/programs/should_pass_fs_close.cap b/tests/programs/should_pass_fs_close.cap new file mode 100644 index 0000000..3b25722 --- /dev/null +++ b/tests/programs/should_pass_fs_close.cap @@ -0,0 +1,19 @@ +package safe +module should_pass_fs_close + +use sys::system +use sys::fs + +pub fn main(rc: RootCap) -> i32 { + let fs = rc.mint_filesystem("./config") + let dir = fs.root_dir() + dir.close() + + let fs2 = rc.mint_filesystem("./config") + fs2.close() + + let rfs = rc.mint_readfs("./config") + rfs.close() + + return 0 +} diff --git a/tests/programs/should_pass_linear_close.cap b/tests/programs/should_pass_linear_close.cap new file mode 100644 index 0000000..4ca70af --- /dev/null +++ b/tests/programs/should_pass_linear_close.cap @@ -0,0 +1,13 @@ +package safe +module should_pass_linear_close + +use sys::system +use sys::fs + +pub fn main(rc: RootCap) -> i32 { + let fs = rc.mint_filesystem("./config") + let dir = fs.root_dir() + let file = dir.open_read("app.txt") + file.close() + return 0 +} diff --git a/tests/programs/string_helpers.cap b/tests/programs/string_helpers.cap index f3a54cb..34781e4 100644 --- a/tests/programs/string_helpers.cap +++ b/tests/programs/string_helpers.cap @@ -10,9 +10,21 @@ pub fn main(rc: RootCap) -> i32 { let b = buf.at(0) let words = "a b c".split_whitespace() let count = words.len() + let trimmed = " hi \n".trim() + let trimmed_start = " hi ".trim_start() + let trimmed_end = " hi ".trim_end() + let lines = "a\nb\n".split_lines() let alloc = rc.mint_alloc_default() alloc.vec_string_free(words) + alloc.vec_string_free(lines) c.assert(n == 3 && b == 97u8 && count == 3) + c.assert(trimmed.len() == 2) + c.assert(trimmed.starts_with_byte(104u8)) + c.assert(trimmed.ends_with_byte(105u8)) + c.assert(trimmed_start.starts_with("hi")) + c.assert(trimmed_end.ends_with("hi")) + c.assert("abc".starts_with_byte(97u8)) + c.assert("abc".ends_with_byte(99u8)) c.println("string ok") return 0 } diff --git a/tests/programs/vec_helpers.cap b/tests/programs/vec_helpers.cap index a6758d4..8154416 100644 --- a/tests/programs/vec_helpers.cap +++ b/tests/programs/vec_helpers.cap @@ -6,6 +6,7 @@ pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() let alloc = rc.mint_alloc_default() let v = alloc.vec_u8_new() + let extra = alloc.vec_u8_new() match v.push(65u8) { Ok(x) => {} Err(e) => { c.println("vec bad"); return 1 } @@ -14,18 +15,42 @@ pub fn main(rc: RootCap) -> i32 { Ok(x) => {} Err(e) => { c.println("vec bad"); return 1 } } + match extra.push(67u8) { + Ok(x) => {} + Err(e) => { c.println("vec bad"); return 1 } + } + match v.extend(extra) { + Ok(x) => {} + Err(e) => { c.println("vec bad"); return 1 } + } let len = v.len() let b = v.get(1) + let filtered = v.filter(66u8) + let mapped = v.map_add(1u8) + let slice = v.slice(1, 2) match (b) { Ok(x) => { - c.assert(len == 2 && x == 66u8) + c.assert(len == 3 && x == 66u8) + c.assert(filtered.len() == 1) + match (mapped.get(0)) { + Ok(v0) => { c.assert(v0 == 66u8) } + Err(e) => { c.println("vec bad"); return 1 } + } + match (slice) { + Ok(s) => { c.assert(s.len() == 2) } + Err(e) => { c.println("vec bad"); return 1 } + } c.println("vec ok") + alloc.vec_u8_free(filtered) + alloc.vec_u8_free(mapped) alloc.vec_u8_free(v) + alloc.vec_u8_free(extra) return 0 } Err(e) => {} } c.println("vec bad") alloc.vec_u8_free(v) + alloc.vec_u8_free(extra) return 1 }