diff --git a/capc/src/codegen/abi_quirks.rs b/capc/src/codegen/abi_quirks.rs new file mode 100644 index 0000000..ecddc0b --- /dev/null +++ b/capc/src/codegen/abi_quirks.rs @@ -0,0 +1,53 @@ +//! ABI quirks and lowering helpers. +//! +//! These helpers are the single source of truth for the special-case ABI +//! shapes we use to lower `Result` values (ResultString and ResultOut). + +use crate::abi::AbiType; + +use super::FnSig; + +/// ResultString ABI uses a u64 length slot across targets. +pub fn result_string_len_bytes() -> u32 { + 8 +} + +/// Return true if the ABI type is lowered as ResultString. +pub fn is_result_string(ty: &AbiType) -> bool { + matches!(ty, AbiType::ResultString) +} + +/// Return true if the ABI type is lowered using ResultOut parameters. +pub fn is_result_out(ty: &AbiType) -> bool { + matches!(ty, AbiType::ResultOut(_, _)) +} + +/// Return true if the ABI type uses any Result lowering. +pub fn is_result_lowering(ty: &AbiType) -> bool { + is_result_string(ty) || is_result_out(ty) +} + +/// Return true if a signature mismatch is explained by Result lowering. +pub fn abi_sig_requires_lowering(abi_sig: &FnSig, sig: &FnSig) -> bool { + abi_sig != sig && is_result_lowering(&abi_sig.ret) +} + +/// Error message used when a layout is requested for a lowered Result ABI. +pub fn result_lowering_layout_error() -> &'static str { + "layout for result out params" +} + +/// Error message used when a Result ABI form is not supported. +pub fn result_abi_mismatch_error() -> &'static str { + "result abi mismatch" +} + +/// Error message used when ResultOut lowering is requested but unsupported. +pub fn result_out_params_error() -> &'static str { + "result out params" +} + +/// Error message used when ResultString lowering is requested but unsupported. +pub fn result_string_params_error() -> &'static str { + "result abi" +} diff --git a/capc/src/codegen/emit.rs b/capc/src/codegen/emit.rs index e2463fb..ac2ca8e 100644 --- a/capc/src/codegen/emit.rs +++ b/capc/src/codegen/emit.rs @@ -18,6 +18,7 @@ use super::{ CodegenError, EnumIndex, Flow, FnInfo, LocalValue, ResultKind, ResultShape, StructLayoutIndex, TypeLayout, ValueRepr, }; +use super::abi_quirks; use super::layout::{align_to, resolve_struct_layout, type_layout_for_abi}; use super::sig_to_clif; @@ -422,7 +423,9 @@ fn emit_hir_expr_inner( if let crate::typeck::Ty::Path(ty_name, args) = &variant.enum_ty.ty { if ty_name == "Result" && args.len() == 2 { let AbiType::Result(ok_abi, err_abi) = &variant.enum_ty.abi else { - return Err(CodegenError::Unsupported("result abi mismatch".to_string())); + return Err(CodegenError::Unsupported( + abi_quirks::result_abi_mismatch_error().to_string(), + )); }; let ok_ty = crate::hir::HirType { ty: args[0].clone(), @@ -538,7 +541,9 @@ fn emit_hir_expr_inner( let ret_value = match &try_expr.ret_ty.ty { crate::typeck::Ty::Path(name, args) if name == "Result" && args.len() == 2 => { let AbiType::Result(ok_abi, _err_abi) = &try_expr.ret_ty.abi else { - return Err(CodegenError::Unsupported("result abi mismatch".to_string())); + return Err(CodegenError::Unsupported( + abi_quirks::result_abi_mismatch_error().to_string(), + )); }; let ok_ty = crate::hir::HirType { ty: args[0].clone(), @@ -621,7 +626,7 @@ fn emit_hir_expr_inner( let mut result_out = None; let abi_sig = info.abi_sig.as_ref().unwrap_or(&info.sig); - if abi_sig.ret == AbiType::ResultString { + if abi_quirks::is_result_string(&abi_sig.ret) { let ptr_ty = module.isa().pointer_type(); let slots = result_string_slots(builder, ptr_ty); push_result_string_out_params(builder, ptr_ty, &slots, &mut args); @@ -680,7 +685,7 @@ fn emit_hir_expr_inner( let results = builder.inst_results(call_inst).to_vec(); // Handle result unpacking (same logic as AST version) - if abi_sig.ret == AbiType::ResultString { + if abi_quirks::is_result_string(&abi_sig.ret) { let tag = results .get(0) .ok_or_else(|| CodegenError::Codegen("missing result tag".to_string()))?; @@ -691,7 +696,9 @@ fn emit_hir_expr_inner( match &info.sig.ret { AbiType::Result(ok_ty, err_ty) => { if **ok_ty != AbiType::String || **err_ty != AbiType::I32 { - return Err(CodegenError::Unsupported("result out params".to_string())); + return Err(CodegenError::Unsupported( + abi_quirks::result_out_params_error().to_string(), + )); } Ok(ValueRepr::Result { tag: *tag, @@ -699,9 +706,11 @@ fn emit_hir_expr_inner( err: Box::new(ValueRepr::Single(err)), }) } - _ => Err(CodegenError::Unsupported("result out params".to_string())), + _ => Err(CodegenError::Unsupported( + abi_quirks::result_out_params_error().to_string(), + )), } - } else if let AbiType::ResultOut(_, _) = &abi_sig.ret { + } else if abi_quirks::is_result_out(&abi_sig.ret) { let tag = results .get(0) .ok_or_else(|| CodegenError::Codegen("missing result tag".to_string()))?; @@ -1213,7 +1222,9 @@ fn store_value_by_ty( return Err(CodegenError::Unsupported("store result".to_string())); }; let AbiType::Result(ok_abi, err_abi) = &ty.abi else { - return Err(CodegenError::Unsupported("result abi mismatch".to_string())); + return Err(CodegenError::Unsupported( + abi_quirks::result_abi_mismatch_error().to_string(), + )); }; let ok_ty = crate::hir::HirType { ty: args[0].clone(), @@ -1360,7 +1371,9 @@ fn load_value_by_ty( Ty::Path(name, args) => { if name == "Result" && args.len() == 2 { let AbiType::Result(ok_abi, err_abi) = &ty.abi else { - return Err(CodegenError::Unsupported("result abi mismatch".to_string())); + return Err(CodegenError::Unsupported( + abi_quirks::result_abi_mismatch_error().to_string(), + )); }; let ok_ty = crate::hir::HirType { ty: args[0].clone(), @@ -1461,14 +1474,9 @@ fn aligned_slot_size(size: u32, align: u32) -> u32 { size.max(1).saturating_add(align.saturating_sub(1)) } -/// ResultString ABI uses a u64 length slot across targets. -fn result_string_len_bytes() -> u32 { - 8 -} - fn result_string_slots(builder: &mut FunctionBuilder, ptr_ty: Type) -> ResultStringSlots { let ptr_align = ptr_ty.bytes() as u32; - let len_bytes = result_string_len_bytes(); + let len_bytes = abi_quirks::result_string_len_bytes(); let len_align = len_bytes; let err_align = 4u32; let slot_ptr = builder.create_sized_stack_slot(ir::StackSlotData::new( @@ -2144,7 +2152,9 @@ fn value_type_for_result_out(ty: &AbiType, ptr_ty: Type) -> Result Ok(ir::types::I64), AbiType::Ptr => Ok(ptr_ty), AbiType::Unit => Err(CodegenError::Unsupported("result out unit".to_string())), - _ => Err(CodegenError::Unsupported("result out params".to_string())), + _ => Err(CodegenError::Unsupported( + abi_quirks::result_out_params_error().to_string(), + )), } } @@ -2175,8 +2185,12 @@ fn zero_value_for_tykind( err: Box::new(err_val), }) } - AbiType::ResultOut(_, _) => Err(CodegenError::Unsupported("result out params".to_string())), - AbiType::ResultString => Err(CodegenError::Unsupported("result abi".to_string())), + AbiType::ResultOut(_, _) => Err(CodegenError::Unsupported( + abi_quirks::result_out_params_error().to_string(), + )), + AbiType::ResultString => Err(CodegenError::Unsupported( + abi_quirks::result_string_params_error().to_string(), + )), } } @@ -2201,7 +2215,9 @@ fn zero_value_for_ty( Ty::Path(name, args) => { if name == "Result" && args.len() == 2 { let AbiType::Result(ok_abi, err_abi) = &ty.abi else { - return Err(CodegenError::Unsupported("result abi mismatch".to_string())); + return Err(CodegenError::Unsupported( + abi_quirks::result_abi_mismatch_error().to_string(), + )); }; let ok_ty = crate::hir::HirType { ty: args[0].clone(), @@ -2309,11 +2325,16 @@ fn value_from_results( err: Box::new(err_val), }) } - AbiType::ResultOut(_, _) => Err(CodegenError::Unsupported("result out params".to_string())), - AbiType::ResultString => Err(CodegenError::Unsupported("result abi".to_string())), + AbiType::ResultOut(_, _) => Err(CodegenError::Unsupported( + abi_quirks::result_out_params_error().to_string(), + )), + AbiType::ResultString => Err(CodegenError::Unsupported( + abi_quirks::result_string_params_error().to_string(), + )), } } +// --- Runtime call emission --- /// Emit a call to a runtime intrinsic with ABI adaptation when needed. pub(super) fn emit_runtime_wrapper_call( builder: &mut FunctionBuilder, @@ -2326,7 +2347,7 @@ pub(super) fn emit_runtime_wrapper_call( let mut out_slots: Option = None; let mut result_out = None; - if abi_sig.ret == AbiType::ResultString { + if abi_quirks::is_result_string(&abi_sig.ret) { let ptr_ty = module.isa().pointer_type(); let slots = result_string_slots(builder, ptr_ty); push_result_string_out_params(builder, ptr_ty, &slots, &mut args); @@ -2378,7 +2399,7 @@ pub(super) fn emit_runtime_wrapper_call( let call_inst = builder.ins().call(local, &args); let results = builder.inst_results(call_inst).to_vec(); - if abi_sig.ret == AbiType::ResultString { + if abi_quirks::is_result_string(&abi_sig.ret) { let tag = results .get(0) .ok_or_else(|| CodegenError::Codegen("missing result tag".to_string()))?; @@ -2388,7 +2409,9 @@ pub(super) fn emit_runtime_wrapper_call( match &info.sig.ret { AbiType::Result(ok_ty, err_ty) => { if **ok_ty != AbiType::String || **err_ty != AbiType::I32 { - return Err(CodegenError::Unsupported("result out params".to_string())); + return Err(CodegenError::Unsupported( + abi_quirks::result_out_params_error().to_string(), + )); } return Ok(ValueRepr::Result { tag: *tag, @@ -2396,11 +2419,13 @@ pub(super) fn emit_runtime_wrapper_call( err: Box::new(ValueRepr::Single(err)), }); } - _ => return Err(CodegenError::Unsupported("result out params".to_string())), + _ => return Err(CodegenError::Unsupported( + abi_quirks::result_out_params_error().to_string(), + )), } } - if let AbiType::ResultOut(_, _) = &abi_sig.ret { + if abi_quirks::is_result_out(&abi_sig.ret) { let tag = results .get(0) .ok_or_else(|| CodegenError::Codegen("missing result tag".to_string()))?; @@ -2447,12 +2472,13 @@ fn ensure_abi_sig_handled(info: &FnInfo) -> Result<(), CodegenError> { if abi_sig == &info.sig { return Ok(()); } - match abi_sig.ret { - AbiType::ResultString | AbiType::ResultOut(_, _) => Ok(()), - _ => Err(CodegenError::Codegen(format!( + if abi_quirks::abi_sig_requires_lowering(abi_sig, &info.sig) { + Ok(()) + } else { + Err(CodegenError::Codegen(format!( "abi signature mismatch for {} without ResultString/ResultOut lowering", info.symbol - ))), + ))) } } @@ -2469,7 +2495,7 @@ mod tests { #[test] fn result_string_len_is_u64() { - assert_eq!(result_string_len_bytes(), 8); + assert_eq!(abi_quirks::result_string_len_bytes(), 8); } #[test] diff --git a/capc/src/codegen/intrinsics.rs b/capc/src/codegen/intrinsics.rs index affd40a..5adc6ff 100644 --- a/capc/src/codegen/intrinsics.rs +++ b/capc/src/codegen/intrinsics.rs @@ -3,6 +3,7 @@ //! Any stdlib function listed here is treated as an intrinsic: its `.cap` body //! is ignored, and codegen emits a direct call to the runtime symbol. If a //! function is not listed here, the Capable implementation is used instead. +//! See `stdlib/README.md` for the stdlib-facing explanation. use std::collections::HashMap; diff --git a/capc/src/codegen/layout.rs b/capc/src/codegen/layout.rs index cee613e..3fcc8ef 100644 --- a/capc/src/codegen/layout.rs +++ b/capc/src/codegen/layout.rs @@ -7,7 +7,8 @@ use std::collections::{HashMap, HashSet}; use cranelift_codegen::ir::Type; use super::{ - CodegenError, EnumIndex, StructFieldLayout, StructLayout, StructLayoutIndex, TypeLayout, + abi_quirks, CodegenError, EnumIndex, StructFieldLayout, StructLayout, StructLayoutIndex, + TypeLayout, }; use crate::abi::AbiType; @@ -178,9 +179,10 @@ fn type_layout_for_hir_type( match &ty.ty { Ty::Path(name, args) if name == "Result" && args.len() == 2 => { let AbiType::Result(ok_abi, err_abi) = &ty.abi else { - return Err(CodegenError::Unsupported( - "result abi mismatch in layout".to_string(), - )); + return Err(CodegenError::Unsupported(format!( + "{} in layout", + abi_quirks::result_abi_mismatch_error() + ))); }; let tag = TypeLayout { size: 1, align: 1 }; let ok = type_layout_for_abi(ok_abi, ptr_ty)?; @@ -265,7 +267,7 @@ pub(super) fn type_layout_for_abi( Ok(TypeLayout { size, align }) } AbiType::ResultOut(_, _) | AbiType::ResultString => Err(CodegenError::Unsupported( - "layout for result out params".to_string(), + abi_quirks::result_lowering_layout_error().to_string(), )), } } diff --git a/capc/src/codegen/mod.rs b/capc/src/codegen/mod.rs index 7f8e23a..31c16ad 100644 --- a/capc/src/codegen/mod.rs +++ b/capc/src/codegen/mod.rs @@ -12,6 +12,7 @@ use std::fs; use std::path::Path; use crate::abi::AbiType; +use crate::error::format_with_context; use cranelift_codegen::ir::{self, AbiParam, Function, InstBuilder, Signature, Type}; use cranelift_codegen::isa::CallConv; use cranelift_codegen::settings::{Configurable, Flags}; @@ -23,6 +24,7 @@ use miette::{Diagnostic, SourceSpan}; use thiserror::Error; mod emit; +mod abi_quirks; mod intrinsics; mod layout; @@ -70,6 +72,21 @@ impl CodegenError { other => CodegenError::spanned(other.to_string(), span), } } + + pub fn with_context(self, context: impl AsRef) -> Self { + match self { + CodegenError::Spanned { + message, + span, + span_raw, + } => CodegenError::Spanned { + message: format_with_context(context, message), + span, + span_raw, + }, + other => CodegenError::Codegen(format_with_context(context, other.to_string())), + } + } } /// Tracks control flow state during code emission. @@ -211,17 +228,7 @@ pub fn build_object( true, )?; } - let missing_intrinsics = runtime_intrinsics - .keys() - .filter(|key| !fn_map.contains_key(*key)) - .cloned() - .collect::>(); - if !missing_intrinsics.is_empty() { - return Err(CodegenError::Codegen(format!( - "runtime intrinsics missing stdlib wrappers: {}", - missing_intrinsics.join(", ") - ))); - } + validate_intrinsics(&fn_map, &runtime_intrinsics)?; for module_ref in &program.user_modules { register_user_functions( module_ref, @@ -363,6 +370,7 @@ pub fn build_object( Ok(()) } +/// Lower a codegen signature into a Cranelift signature. fn sig_to_clif(sig: &FnSig, ptr_ty: Type) -> Signature { let mut signature = Signature::new(CallConv::SystemV); for param in &sig.params { @@ -372,6 +380,7 @@ fn sig_to_clif(sig: &FnSig, ptr_ty: Type) -> Signature { signature } +/// Append the ABI parameters for a single type. fn append_ty_params(signature: &mut Signature, ty: &AbiType, ptr_ty: Type) { match ty { AbiType::Unit => {} @@ -406,6 +415,7 @@ fn append_ty_params(signature: &mut Signature, ty: &AbiType, ptr_ty: Type) { } } +/// Append the ABI return values for a single type. fn append_ty_returns(signature: &mut Signature, ty: &AbiType, ptr_ty: Type) { match ty { AbiType::Unit => {} @@ -433,10 +443,12 @@ fn append_ty_returns(signature: &mut Signature, ty: &AbiType, ptr_ty: Type) { } } +/// Register runtime-backed intrinsics for stdlib symbols. fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { intrinsics::register_runtime_intrinsics(ptr_ty) } +/// Register Capable-defined functions (stdlib or user) into the codegen map. fn register_user_functions( module: &crate::hir::HirModule, entry: &crate::hir::HirModule, @@ -489,6 +501,7 @@ fn register_user_functions( Ok(()) } +/// Register extern functions defined in HIR into the codegen map. fn register_extern_functions_from_hir( module: &crate::hir::HirModule, map: &mut HashMap, @@ -518,6 +531,38 @@ fn register_extern_functions_from_hir( Ok(()) } +/// Validate stdlib/runtime intrinsic coverage in both directions. +fn validate_intrinsics( + fn_map: &HashMap, + runtime_intrinsics: &HashMap, +) -> Result<(), CodegenError> { + let missing_wrappers = runtime_intrinsics + .keys() + .filter(|key| !fn_map.contains_key(*key)) + .cloned() + .collect::>(); + if !missing_wrappers.is_empty() { + return Err(CodegenError::Codegen(format!( + "runtime intrinsics missing stdlib wrappers: {}", + missing_wrappers.join(", ") + ))); + } + + let unknown_wrappers = fn_map + .iter() + .filter(|(key, info)| info.runtime_symbol.is_some() && !runtime_intrinsics.contains_key(*key)) + .map(|(key, _)| key.clone()) + .collect::>(); + if !unknown_wrappers.is_empty() { + return Err(CodegenError::Codegen(format!( + "stdlib wrappers missing intrinsic registration: {}", + unknown_wrappers.join(", ") + ))); + } + Ok(()) +} + +/// Build the stable, linkable symbol name for a function. fn mangle_symbol(module_name: &str, func_name: &str) -> String { let mut out = String::from("capable_"); out.push_str(&module_name.replace('.', "_")); diff --git a/capc/src/error.rs b/capc/src/error.rs index e9be466..61fe5d7 100644 --- a/capc/src/error.rs +++ b/capc/src/error.rs @@ -5,6 +5,17 @@ use thiserror::Error; use crate::ast::Span; +/// Prefix an error message with context for consistent diagnostics. +pub fn format_with_context(context: impl AsRef, message: impl AsRef) -> String { + let prefix = context.as_ref(); + let message = message.as_ref(); + if prefix.is_empty() { + message.to_string() + } else { + format!("{prefix}: {message}") + } +} + #[derive(Debug, Error, Diagnostic)] #[error("{message}")] #[allow(unused)] @@ -25,10 +36,7 @@ impl ParseError { } pub fn with_context(mut self, context: impl AsRef) -> Self { - let prefix = context.as_ref(); - if !prefix.is_empty() { - self.message = format!("{prefix}: {}", self.message); - } + self.message = format_with_context(context, &self.message); self } @@ -61,10 +69,7 @@ impl TypeError { } pub fn with_context(mut self, context: impl AsRef) -> Self { - let prefix = context.as_ref(); - if !prefix.is_empty() { - self.message = format!("{prefix}: {}", self.message); - } + self.message = format_with_context(context, &self.message); self } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e13083c..c0f0674 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -95,6 +95,15 @@ fn take_handle( table.remove(&handle) } +fn with_table( + table: &LazyLock>>, + label: &'static str, + f: impl FnOnce(&mut HashMap) -> R, +) -> R { + let mut table = table.lock().expect(label); + f(&mut table) +} + #[no_mangle] pub extern "C" fn capable_rt_mint_console(_sys: Handle) -> Handle { new_handle() @@ -418,14 +427,15 @@ pub extern "C" fn capable_rt_alloc_default(_sys: Handle) -> Handle { pub extern "C" fn capable_rt_slice_from_ptr(_sys: Handle, ptr: *mut u8, len: i32) -> Handle { let len = len.max(0) as usize; let handle = new_handle(); - let mut table = SLICES.lock().expect("slice table"); - table.insert( - handle, - SliceState { - ptr: ptr as usize, - len, - }, - ); + with_table(&SLICES, "slice table", |table| { + table.insert( + handle, + SliceState { + ptr: ptr as usize, + len, + }, + ); + }); handle } @@ -433,24 +443,26 @@ pub extern "C" fn capable_rt_slice_from_ptr(_sys: Handle, ptr: *mut u8, len: i32 pub extern "C" fn capable_rt_mut_slice_from_ptr(_sys: Handle, ptr: *mut u8, len: i32) -> Handle { let len = len.max(0) as usize; let handle = new_handle(); - let mut table = SLICES.lock().expect("slice table"); - table.insert( - handle, - SliceState { - ptr: ptr as usize, - len, - }, - ); + with_table(&SLICES, "slice table", |table| { + table.insert( + handle, + SliceState { + ptr: ptr as usize, + len, + }, + ); + }); handle } #[no_mangle] pub extern "C" fn capable_rt_slice_len(slice: Handle) -> i32 { - let table = SLICES.lock().expect("slice table"); - table - .get(&slice) - .map(|state| state.len.min(i32::MAX as usize) as i32) - .unwrap_or(0) + with_table(&SLICES, "slice table", |table| { + table + .get(&slice) + .map(|state| state.len.min(i32::MAX as usize) as i32) + .unwrap_or(0) + }) } #[no_mangle] @@ -459,15 +471,16 @@ pub extern "C" fn capable_rt_slice_at(slice: Handle, index: i32) -> u8 { Ok(idx) => idx, Err(_) => return 0, }; - let table = SLICES.lock().expect("slice table"); - let Some(state) = table.get(&slice) else { - return 0; - }; - if state.ptr == 0 || idx >= state.len { - return 0; - } - let ptr = state.ptr as *mut u8; - unsafe { *ptr.add(idx) } + with_table(&SLICES, "slice table", |table| { + let Some(state) = table.get(&slice) else { + return 0; + }; + if state.ptr == 0 || idx >= state.len { + return 0; + } + let ptr = state.ptr as *mut u8; + unsafe { *ptr.add(idx) } + }) } #[no_mangle] @@ -476,15 +489,16 @@ pub extern "C" fn capable_rt_mut_slice_at(slice: Handle, index: i32) -> u8 { Ok(idx) => idx, Err(_) => return 0, }; - let table = SLICES.lock().expect("slice table"); - let Some(state) = table.get(&slice) else { - return 0; - }; - if state.ptr == 0 || idx >= state.len { - return 0; - } - let ptr = state.ptr as *mut u8; - unsafe { *ptr.add(idx) } + with_table(&SLICES, "slice table", |table| { + let Some(state) = table.get(&slice) else { + return 0; + }; + if state.ptr == 0 || idx >= state.len { + return 0; + } + let ptr = state.ptr as *mut u8; + unsafe { *ptr.add(idx) } + }) } #[no_mangle] @@ -514,8 +528,9 @@ pub extern "C" fn capable_rt_buffer_new( } data.resize(len, 0); let handle = new_handle(); - let mut table = BUFFERS.lock().expect("buffer table"); - table.insert(handle, data); + with_table(&BUFFERS, "buffer table", |table| { + table.insert(handle, data); + }); unsafe { if !out_ok.is_null() { *out_ok = handle; @@ -526,11 +541,12 @@ pub extern "C" fn capable_rt_buffer_new( #[no_mangle] pub extern "C" fn capable_rt_buffer_len(buffer: Handle) -> i32 { - let table = BUFFERS.lock().expect("buffer table"); - table - .get(&buffer) - .map(|data| data.len().min(i32::MAX as usize) as i32) - .unwrap_or(0) + 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] @@ -539,98 +555,88 @@ pub extern "C" fn capable_rt_buffer_push( value: u8, out_err: *mut i32, ) -> u8 { - let mut table = BUFFERS.lock().expect("buffer table"); - let Some(data) = table.get_mut(&buffer) else { - unsafe { - if !out_err.is_null() { - *out_err = 0; + 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; + }; + if data.try_reserve(1).is_err() { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } } + return 1; } - return 1; - } - data.push(value); - 0 + data.push(value); + 0 + }) } #[no_mangle] pub extern "C" fn capable_rt_buffer_free(_alloc: Handle, buffer: Handle) { - let mut table = BUFFERS.lock().expect("buffer table"); - table.remove(&buffer); + with_table(&BUFFERS, "buffer table", |table| { + table.remove(&buffer); + }); } #[no_mangle] pub extern "C" fn capable_rt_buffer_as_slice(buffer: Handle) -> Handle { - let table = BUFFERS.lock().expect("buffer table"); - let Some(data) = table.get(&buffer) else { + let Some((ptr, len)) = with_table(&BUFFERS, "buffer table", |table| { + let Some(data) = table.get(&buffer) else { + return None; + }; + let ptr = if data.is_empty() { 0 } else { data.as_ptr() as usize }; + Some((ptr, data.len())) + }) else { return 0; }; let handle = new_handle(); - let ptr = if data.is_empty() { - 0 - } else { - data.as_ptr() as usize - }; - let len = data.len(); - drop(table); - let mut slices = SLICES.lock().expect("slice table"); - slices.insert( - handle, - SliceState { - ptr, - len, - }, - ); + with_table(&SLICES, "slice table", |table| { + table.insert(handle, SliceState { ptr, len }); + }); handle } #[no_mangle] pub extern "C" fn capable_rt_buffer_as_mut_slice(buffer: Handle) -> Handle { - let mut table = BUFFERS.lock().expect("buffer table"); - let Some(data) = table.get_mut(&buffer) else { + let Some((ptr, len)) = with_table(&BUFFERS, "buffer table", |table| { + let Some(data) = table.get_mut(&buffer) else { + return None; + }; + let ptr = if data.is_empty() { 0 } else { data.as_mut_ptr() as usize }; + Some((ptr, data.len())) + }) else { return 0; }; let handle = new_handle(); - let ptr = if data.is_empty() { - 0 - } else { - data.as_mut_ptr() as usize - }; - let len = data.len(); - drop(table); - let mut slices = SLICES.lock().expect("slice table"); - slices.insert( - handle, - SliceState { - ptr, - len, - }, - ); + with_table(&SLICES, "slice table", |table| { + table.insert(handle, SliceState { ptr, len }); + }); handle } #[no_mangle] pub extern "C" fn capable_rt_vec_u8_new(_alloc: Handle) -> Handle { let handle = new_handle(); - let mut table = VECS_U8.lock().expect("vec u8 table"); - table.insert(handle, Vec::new()); + with_table(&VECS_U8, "vec u8 table", |table| { + table.insert(handle, Vec::new()); + }); handle } #[no_mangle] pub extern "C" fn capable_rt_vec_u8_len(vec: Handle) -> i32 { - let table = VECS_U8.lock().expect("vec u8 table"); - table - .get(&vec) - .map(|data| data.len().min(i32::MAX as usize) as i32) - .unwrap_or(0) + with_table(&VECS_U8, "vec u8 table", |table| { + table + .get(&vec) + .map(|data| data.len().min(i32::MAX as usize) as i32) + .unwrap_or(0) + }) } #[no_mangle] @@ -651,29 +657,30 @@ pub extern "C" fn capable_rt_vec_u8_get( return 1; } }; - let table = VECS_U8.lock().expect("vec u8 table"); - let Some(data) = table.get(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; + with_table(&VECS_U8, "vec u8 table", |table| { + let Some(data) = table.get(&vec) else { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::OutOfRange as i32; + } } - } - return 1; - }; - let Some(value) = data.get(idx) else { + return 1; + }; + let Some(value) = data.get(idx) else { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::OutOfRange as i32; + } + } + return 1; + }; unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; + if !out_ok.is_null() { + *out_ok = *value; } } - return 1; - }; - unsafe { - if !out_ok.is_null() { - *out_ok = *value; - } - } - 0 + 0 + }) } #[no_mangle] @@ -694,25 +701,26 @@ pub extern "C" fn capable_rt_vec_u8_set( return 1; } }; - let mut table = VECS_U8.lock().expect("vec u8 table"); - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; + with_table(&VECS_U8, "vec u8 table", |table| { + let Some(data) = table.get_mut(&vec) else { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::OutOfRange as i32; + } } - } - return 1; - }; - if idx >= data.len() { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; + return 1; + }; + if idx >= data.len() { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::OutOfRange as i32; + } } + return 1; } - return 1; - } - data[idx] = value; - 0 + data[idx] = value; + 0 + }) } #[no_mangle] @@ -721,25 +729,26 @@ pub extern "C" fn capable_rt_vec_u8_push( value: u8, out_err: *mut i32, ) -> u8 { - let mut table = VECS_U8.lock().expect("vec u8 table"); - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = 0; + with_table(&VECS_U8, "vec u8 table", |table| { + let Some(data) = table.get_mut(&vec) 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; + }; + if data.try_reserve(1).is_err() { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } } + return 1; } - return 1; - } - data.push(value); - 0 + data.push(value); + 0 + }) } #[no_mangle] @@ -748,77 +757,74 @@ pub extern "C" fn capable_rt_vec_u8_pop( out_ok: *mut u8, out_err: *mut i32, ) -> u8 { - let mut table = VECS_U8.lock().expect("vec u8 table"); - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::Empty as i32; + with_table(&VECS_U8, "vec u8 table", |table| { + let Some(data) = table.get_mut(&vec) else { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::Empty as i32; + } } - } - return 1; - }; - let Some(value) = data.pop() else { + return 1; + }; + let Some(value) = data.pop() else { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::Empty as i32; + } + } + return 1; + }; unsafe { - if !out_err.is_null() { - *out_err = VecErr::Empty as i32; + if !out_ok.is_null() { + *out_ok = value; } } - return 1; - }; - unsafe { - if !out_ok.is_null() { - *out_ok = value; - } - } - 0 + 0 + }) } #[no_mangle] pub extern "C" fn capable_rt_vec_u8_as_slice(vec: Handle) -> Handle { - let table = VECS_U8.lock().expect("vec u8 table"); - let Some(data) = table.get(&vec) else { + let Some((ptr, len)) = with_table(&VECS_U8, "vec u8 table", |table| { + let Some(data) = table.get(&vec) else { + return None; + }; + let ptr = if data.is_empty() { 0 } else { data.as_ptr() as usize }; + Some((ptr, data.len())) + }) else { return 0; }; let handle = new_handle(); - let ptr = if data.is_empty() { - 0 - } else { - data.as_ptr() as usize - }; - let len = data.len(); - drop(table); - let mut slices = SLICES.lock().expect("slice table"); - slices.insert( - handle, - SliceState { - ptr, - len, - }, - ); + with_table(&SLICES, "slice table", |table| { + table.insert(handle, SliceState { ptr, len }); + }); handle } #[no_mangle] pub extern "C" fn capable_rt_vec_u8_free(_alloc: Handle, vec: Handle) { - let mut table = VECS_U8.lock().expect("vec u8 table"); - table.remove(&vec); + with_table(&VECS_U8, "vec u8 table", |table| { + table.remove(&vec); + }); } #[no_mangle] pub extern "C" fn capable_rt_vec_i32_new(_alloc: Handle) -> Handle { let handle = new_handle(); - let mut table = VECS_I32.lock().expect("vec i32 table"); - table.insert(handle, Vec::new()); + with_table(&VECS_I32, "vec i32 table", |table| { + table.insert(handle, Vec::new()); + }); handle } #[no_mangle] pub extern "C" fn capable_rt_vec_i32_len(vec: Handle) -> i32 { - let table = VECS_I32.lock().expect("vec i32 table"); - table - .get(&vec) - .map(|data| data.len().min(i32::MAX as usize) as i32) - .unwrap_or(0) + with_table(&VECS_I32, "vec i32 table", |table| { + table + .get(&vec) + .map(|data| data.len().min(i32::MAX as usize) as i32) + .unwrap_or(0) + }) } #[no_mangle] @@ -839,29 +845,30 @@ pub extern "C" fn capable_rt_vec_i32_get( return 1; } }; - let table = VECS_I32.lock().expect("vec i32 table"); - let Some(data) = table.get(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; + with_table(&VECS_I32, "vec i32 table", |table| { + let Some(data) = table.get(&vec) else { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::OutOfRange as i32; + } } - } - return 1; - }; - let Some(value) = data.get(idx) else { + return 1; + }; + let Some(value) = data.get(idx) else { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::OutOfRange as i32; + } + } + return 1; + }; unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; + if !out_ok.is_null() { + *out_ok = *value; } } - return 1; - }; - unsafe { - if !out_ok.is_null() { - *out_ok = *value; - } - } - 0 + 0 + }) } #[no_mangle] @@ -882,25 +889,26 @@ pub extern "C" fn capable_rt_vec_i32_set( return 1; } }; - let mut table = VECS_I32.lock().expect("vec i32 table"); - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; + with_table(&VECS_I32, "vec i32 table", |table| { + let Some(data) = table.get_mut(&vec) else { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::OutOfRange as i32; + } } - } - return 1; - }; - if idx >= data.len() { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; + return 1; + }; + if idx >= data.len() { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::OutOfRange as i32; + } } + return 1; } - return 1; - } - data[idx] = value; - 0 + data[idx] = value; + 0 + }) } #[no_mangle] @@ -909,25 +917,26 @@ pub extern "C" fn capable_rt_vec_i32_push( value: i32, out_err: *mut i32, ) -> u8 { - let mut table = VECS_I32.lock().expect("vec i32 table"); - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = 0; + with_table(&VECS_I32, "vec i32 table", |table| { + let Some(data) = table.get_mut(&vec) 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; + }; + if data.try_reserve(1).is_err() { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } } + return 1; } - return 1; - } - data.push(value); - 0 + data.push(value); + 0 + }) } #[no_mangle] @@ -936,52 +945,56 @@ pub extern "C" fn capable_rt_vec_i32_pop( out_ok: *mut i32, out_err: *mut i32, ) -> u8 { - let mut table = VECS_I32.lock().expect("vec i32 table"); - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::Empty as i32; + with_table(&VECS_I32, "vec i32 table", |table| { + let Some(data) = table.get_mut(&vec) else { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::Empty as i32; + } } - } - return 1; - }; - let Some(value) = data.pop() else { + return 1; + }; + let Some(value) = data.pop() else { + unsafe { + if !out_err.is_null() { + *out_err = VecErr::Empty as i32; + } + } + return 1; + }; unsafe { - if !out_err.is_null() { - *out_err = VecErr::Empty as i32; + if !out_ok.is_null() { + *out_ok = value; } } - return 1; - }; - unsafe { - if !out_ok.is_null() { - *out_ok = value; - } - } - 0 + 0 + }) } #[no_mangle] pub extern "C" fn capable_rt_vec_i32_free(_alloc: Handle, vec: Handle) { - let mut table = VECS_I32.lock().expect("vec i32 table"); - table.remove(&vec); + with_table(&VECS_I32, "vec i32 table", |table| { + table.remove(&vec); + }); } #[no_mangle] pub extern "C" fn capable_rt_vec_string_new(_alloc: Handle) -> Handle { let handle = new_handle(); - let mut table = VECS_STRING.lock().expect("vec string table"); - table.insert(handle, Vec::new()); + with_table(&VECS_STRING, "vec string table", |table| { + table.insert(handle, Vec::new()); + }); handle } #[no_mangle] pub extern "C" fn capable_rt_vec_string_len(vec: Handle) -> i32 { - let table = VECS_STRING.lock().expect("vec string table"); - table - .get(&vec) - .map(|data| data.len().min(i32::MAX as usize) as i32) - .unwrap_or(0) + with_table(&VECS_STRING, "vec string table", |table| { + table + .get(&vec) + .map(|data| data.len().min(i32::MAX as usize) as i32) + .unwrap_or(0) + }) } #[no_mangle] @@ -998,14 +1011,15 @@ pub extern "C" fn capable_rt_vec_string_get( return write_string_result(out_ptr, out_len, out_err, Err(VecErr::OutOfRange as i32)); } }; - let table = VECS_STRING.lock().expect("vec string table"); - let Some(data) = table.get(&vec) else { - return write_string_result(out_ptr, out_len, out_err, Err(VecErr::OutOfRange as i32)); - }; - let Some(value) = data.get(idx) else { - return write_string_result(out_ptr, out_len, out_err, Err(VecErr::OutOfRange as i32)); - }; - write_string_result(out_ptr, out_len, out_err, Ok(value.clone())) + with_table(&VECS_STRING, "vec string table", |table| { + let Some(data) = table.get(&vec) else { + return write_string_result(out_ptr, out_len, out_err, Err(VecErr::OutOfRange as i32)); + }; + let Some(value) = data.get(idx) else { + return write_string_result(out_ptr, out_len, out_err, Err(VecErr::OutOfRange as i32)); + }; + write_string_result(out_ptr, out_len, out_err, Ok(value.clone())) + }) } #[no_mangle] @@ -1024,25 +1038,26 @@ pub extern "C" fn capable_rt_vec_string_push( } return 1; }; - let mut table = VECS_STRING.lock().expect("vec string table"); - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = 0; + with_table(&VECS_STRING, "vec string table", |table| { + let Some(data) = table.get_mut(&vec) 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; + }; + if data.try_reserve(1).is_err() { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } } + return 1; } - return 1; - } - data.push(value); - 0 + data.push(value); + 0 + }) } #[no_mangle] @@ -1052,20 +1067,22 @@ pub extern "C" fn capable_rt_vec_string_pop( out_len: *mut u64, out_err: *mut i32, ) -> u8 { - let mut table = VECS_STRING.lock().expect("vec string table"); - let Some(data) = table.get_mut(&vec) else { - return write_string_result(out_ptr, out_len, out_err, Err(VecErr::Empty as i32)); - }; - let Some(value) = data.pop() else { - return write_string_result(out_ptr, out_len, out_err, Err(VecErr::Empty as i32)); - }; - write_string_result(out_ptr, out_len, out_err, Ok(value)) + with_table(&VECS_STRING, "vec string table", |table| { + let Some(data) = table.get_mut(&vec) else { + return write_string_result(out_ptr, out_len, out_err, Err(VecErr::Empty as i32)); + }; + let Some(value) = data.pop() else { + return write_string_result(out_ptr, out_len, out_err, Err(VecErr::Empty as i32)); + }; + write_string_result(out_ptr, out_len, out_err, Ok(value)) + }) } #[no_mangle] pub extern "C" fn capable_rt_vec_string_free(_alloc: Handle, vec: Handle) { - let mut table = VECS_STRING.lock().expect("vec string table"); - table.remove(&vec); + with_table(&VECS_STRING, "vec string table", |table| { + table.remove(&vec); + }); } #[no_mangle] @@ -1079,8 +1096,9 @@ pub extern "C" fn capable_rt_string_split_whitespace( vec.extend(value.split_whitespace().map(|s| s.to_string())); } let handle = new_handle(); - let mut table = VECS_STRING.lock().expect("vec string table"); - table.insert(handle, vec); + with_table(&VECS_STRING, "vec string table", |table| { + table.insert(handle, vec); + }); handle } @@ -1106,8 +1124,9 @@ pub extern "C" fn capable_rt_string_split( vec.push(part.to_string()); } let handle = new_handle(); - let mut table = VECS_STRING.lock().expect("vec string table"); - table.insert(handle, vec); + with_table(&VECS_STRING, "vec string table", |table| { + table.insert(handle, vec); + }); handle } @@ -1122,8 +1141,9 @@ pub extern "C" fn capable_rt_string_split_lines(ptr: *const u8, len: usize) -> H } } let handle = new_handle(); - let mut table = VECS_STRING.lock().expect("vec string table"); - table.insert(handle, vec); + with_table(&VECS_STRING, "vec string table", |table| { + table.insert(handle, vec); + }); handle } @@ -1225,14 +1245,15 @@ pub extern "C" fn capable_rt_string_byte_at( #[no_mangle] pub extern "C" fn capable_rt_string_as_slice(ptr: *const u8, len: usize) -> Handle { let handle = new_handle(); - let mut table = SLICES.lock().expect("slice table"); - table.insert( - handle, - SliceState { - ptr: ptr as usize, - len, - }, - ); + with_table(&SLICES, "slice table", |table| { + table.insert( + handle, + SliceState { + ptr: ptr as usize, + len, + }, + ); + }); handle } diff --git a/stdlib/README.md b/stdlib/README.md new file mode 100644 index 0000000..bcf5cd4 --- /dev/null +++ b/stdlib/README.md @@ -0,0 +1,10 @@ +# Standard Library Notes + +Some stdlib methods are implemented by the runtime instead of Capable code. +If a method has a stub body (like `return 0` or `return ()`), its actual +implementation lives in the runtime and the compiler treats it as an intrinsic. + +The single source of truth for these runtime-backed intrinsics is: +- `capc/src/codegen/intrinsics.rs` + +Anything not listed there is a real Capable implementation.