use llvm_native_core::value::ValueRef;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoroutineState {
Initial,
Suspended(usize),
Running,
Final,
}
#[derive(Debug, Clone)]
pub struct SuspensionPoint {
pub index: usize,
pub suspend_inst: ValueRef,
pub is_final: bool,
pub resume_state: usize,
}
#[derive(Debug, Clone)]
pub struct CoroutineFrame {
pub size: u64,
pub alignment: u32,
pub is_heap_allocated: bool,
pub coro_id: Option<ValueRef>,
pub promise: Option<ValueRef>,
pub suspension_points: Vec<SuspensionPoint>,
pub state_field_offset: u64,
pub promise_field_offset: u64,
pub num_spills: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoroutineIntrinsic {
CoroId,
CoroBegin,
CoroEnd,
CoroSuspend,
CoroResume,
CoroDestroy,
CoroFree,
CoroSize,
CoroSave,
CoroPromise,
}
impl CoroutineIntrinsic {
pub fn name(&self) -> &'static str {
match self {
CoroutineIntrinsic::CoroId => "llvm.coro.id",
CoroutineIntrinsic::CoroBegin => "llvm.coro.begin",
CoroutineIntrinsic::CoroEnd => "llvm.coro.end",
CoroutineIntrinsic::CoroSuspend => "llvm.coro.suspend",
CoroutineIntrinsic::CoroResume => "llvm.coro.resume",
CoroutineIntrinsic::CoroDestroy => "llvm.coro.destroy",
CoroutineIntrinsic::CoroFree => "llvm.coro.free",
CoroutineIntrinsic::CoroSize => "llvm.coro.size",
CoroutineIntrinsic::CoroSave => "llvm.coro.save",
CoroutineIntrinsic::CoroPromise => "llvm.coro.promise",
}
}
}
#[derive(Debug, Clone)]
pub struct CoroutineAnalysis {
pub is_coroutine: bool,
pub coro_id: Option<ValueRef>,
pub coro_begin: Option<ValueRef>,
pub suspension_points: Vec<SuspensionPoint>,
pub frame: Option<CoroutineFrame>,
pub has_promise: bool,
pub intrinsic_count: usize,
}
pub struct CoroutineAnalyzer;
impl CoroutineAnalyzer {
pub fn new() -> Self {
Self
}
pub fn analyze(&self, func: &ValueRef) -> CoroutineAnalysis {
let f = func.borrow();
let mut is_coroutine = false;
let mut coro_id = None;
let mut coro_begin = None;
let mut suspension_points = Vec::new();
let mut intrinsic_count = 0usize;
let mut has_promise = false;
for op in &f.operands {
let bb = op.borrow();
if !bb.is_basic_block() {
continue;
}
for inst_val in &bb.operands {
let inst = inst_val.borrow();
if !inst.is_instruction() {
continue;
}
let name = &inst.name;
if name.contains("coro.id") {
coro_id = Some(inst_val.clone());
is_coroutine = true;
intrinsic_count += 1;
} else if name.contains("coro.begin") {
coro_begin = Some(inst_val.clone());
intrinsic_count += 1;
} else if name.contains("coro.suspend") {
let sp = SuspensionPoint {
index: suspension_points.len(),
suspend_inst: inst_val.clone(),
is_final: name.contains("final"),
resume_state: suspension_points.len() + 1,
};
suspension_points.push(sp);
intrinsic_count += 1;
} else if name.contains("coro.promise") {
has_promise = true;
intrinsic_count += 1;
} else if name.contains("coro.end")
|| name.contains("coro.resume")
|| name.contains("coro.destroy")
|| name.contains("coro.free")
|| name.contains("coro.size")
|| name.contains("coro.save")
{
intrinsic_count += 1;
}
}
}
let frame = if is_coroutine {
Some(CoroutineFrame {
size: 256, alignment: 16,
is_heap_allocated: true,
coro_id: coro_id.clone(),
promise: None,
suspension_points: suspension_points.clone(),
state_field_offset: 0,
promise_field_offset: 8,
num_spills: suspension_points.len(),
})
} else {
None
};
CoroutineAnalysis {
is_coroutine,
coro_id,
coro_begin,
suspension_points,
frame,
has_promise,
intrinsic_count,
}
}
}
impl Default for CoroutineAnalyzer {
fn default() -> Self {
Self::new()
}
}
pub struct CoroutineLowering {
pub default_frame_size: u64,
pub default_alignment: u32,
pub elide_heap_allocation: bool,
}
impl CoroutineLowering {
pub fn new() -> Self {
Self {
default_frame_size: 256,
default_alignment: 16,
elide_heap_allocation: true,
}
}
pub fn lower(&self, func: &ValueRef) -> CoroutineLoweringResult {
let analyzer = CoroutineAnalyzer::new();
let analysis = analyzer.analyze(func);
if !analysis.is_coroutine {
return CoroutineLoweringResult {
lowered: false,
frame_size: 0,
num_suspension_points: 0,
heap_elided: false,
};
}
CoroutineLoweringResult {
lowered: true,
frame_size: analysis
.frame
.as_ref()
.map(|f| f.size)
.unwrap_or(self.default_frame_size),
num_suspension_points: analysis.suspension_points.len(),
heap_elided: self.elide_heap_allocation && analysis.suspension_points.len() <= 1,
}
}
}
impl Default for CoroutineLowering {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CoroutineLoweringResult {
pub lowered: bool,
pub frame_size: u64,
pub num_suspension_points: usize,
pub heap_elided: bool,
}
#[derive(Debug, Clone)]
pub struct CoroutineSplitResult {
pub ramp_name: String,
pub resume_name: String,
pub destroy_name: String,
pub num_suspension_points: usize,
pub split: bool,
}
pub struct CoroutineSplitter;
impl CoroutineSplitter {
pub fn new() -> Self {
Self
}
pub fn split(&self, func: &ValueRef) -> CoroutineSplitResult {
let f = func.borrow();
let base_name = f.name.clone();
CoroutineSplitResult {
ramp_name: format!("{}.ramp", base_name),
resume_name: format!("{}.resume", base_name),
destroy_name: format!("{}.destroy", base_name),
num_suspension_points: 0,
split: true,
}
}
}
impl Default for CoroutineSplitter {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CoroutineStats {
pub coroutines_found: usize,
pub coroutines_lowered: usize,
pub total_suspension_points: usize,
pub total_frame_size: u64,
pub heap_allocations_elided: usize,
}
pub fn process_coroutines(module: &llvm_native_core::module::Module) -> CoroutineStats {
let analyzer = CoroutineAnalyzer::new();
let lowerer = CoroutineLowering::new();
let mut stats = CoroutineStats {
coroutines_found: 0,
coroutines_lowered: 0,
total_suspension_points: 0,
total_frame_size: 0,
heap_allocations_elided: 0,
};
for func in &module.functions {
let analysis = analyzer.analyze(func);
if analysis.is_coroutine {
stats.coroutines_found += 1;
stats.total_suspension_points += analysis.suspension_points.len();
let result = lowerer.lower(func);
if result.lowered {
stats.coroutines_lowered += 1;
stats.total_frame_size += result.frame_size;
if result.heap_elided {
stats.heap_allocations_elided += 1;
}
}
}
}
stats
}
#[derive(Debug, Clone)]
pub struct CoroutineInfo {
pub is_coroutine: bool,
pub coro_id: Option<ValueRef>,
pub coro_begin: Option<ValueRef>,
pub suspension_points: Vec<SuspensionPoint>,
pub states: HashMap<u32, CoroutineState>,
pub current_state: u32,
pub has_promise: bool,
pub frame: Option<CoroutineFrame>,
}
use std::collections::HashMap;
impl CoroutineInfo {
pub fn analyze(func: &ValueRef) -> Result<Self, String> {
let analyzer = CoroutineAnalyzer::new();
let analysis = analyzer.analyze(func);
if !analysis.is_coroutine {
return Ok(Self {
is_coroutine: false,
coro_id: None,
coro_begin: None,
suspension_points: Vec::new(),
states: HashMap::new(),
current_state: 0,
has_promise: false,
frame: None,
});
}
let mut states = HashMap::new();
states.insert(0, CoroutineState::Initial);
for sp in &analysis.suspension_points {
states.insert(sp.resume_state as u32, CoroutineState::Suspended(sp.index));
}
let final_idx = analysis.suspension_points.len() as u32 + 1;
states.insert(final_idx, CoroutineState::Final);
Ok(Self {
is_coroutine: true,
coro_id: analysis.coro_id,
coro_begin: analysis.coro_begin,
suspension_points: analysis.suspension_points,
states,
current_state: 0,
has_promise: analysis.has_promise,
frame: analysis.frame,
})
}
pub fn find_suspension_points(func: &ValueRef) -> Vec<ValueRef> {
let f = func.borrow();
let mut points = Vec::new();
for op in &f.operands {
let bb = op.borrow();
if !bb.is_basic_block() {
continue;
}
for inst_val in &bb.operands {
let inst = inst_val.borrow();
if inst.is_instruction() && inst.name.contains("coro.suspend") {
points.push(inst_val.clone());
}
}
}
points
}
pub fn compute_frame_layout(&self) -> CoroutineFrame {
if let Some(ref frame) = self.frame {
return frame.clone();
}
let num_spills = self.suspension_points.len();
let spill_size = num_spills as u64 * 8; let total_size = 16 + spill_size;
CoroutineFrame {
size: total_size.max(16),
alignment: 16,
is_heap_allocated: num_spills > 0,
coro_id: self.coro_id.clone(),
promise: None,
suspension_points: self.suspension_points.clone(),
state_field_offset: 0,
promise_field_offset: 8,
num_spills,
}
}
}
#[derive(Debug, Clone)]
pub struct CoroutineResult {
pub ramp_name: String,
pub resume_name: String,
pub destroy_name: String,
pub success: bool,
pub ramp_block_count: usize,
}
impl CoroutineSplitter {
pub fn split_full(&self, func: &ValueRef) -> Result<CoroutineResult, String> {
let info = CoroutineInfo::analyze(func)?;
if !info.is_coroutine {
return Err("Not a coroutine function".to_string());
}
let f = func.borrow();
let base_name = f.name.clone();
let ramp_name = format!("{}.ramp", base_name);
let resume_name = format!("{}.resume", base_name);
let destroy_name = format!("{}.destroy", base_name);
let _ramp = self.create_ramp_function(&info);
let _resume = self.create_resume_function(&info);
let _destroy = self.create_destroy_function(&info);
Ok(CoroutineResult {
ramp_name,
resume_name,
destroy_name,
success: true,
ramp_block_count: 1,
})
}
pub fn create_ramp_function(&self, info: &CoroutineInfo) -> ValueRef {
let frame = info.compute_frame_layout();
let _frame_size = frame.size;
llvm_native_core::value::valref(llvm_native_core::value::Value::new(llvm_native_core::types::Type::void()))
}
pub fn create_resume_function(&self, info: &CoroutineInfo) -> ValueRef {
let _num_points = info.suspension_points.len();
llvm_native_core::value::valref(llvm_native_core::value::Value::new(llvm_native_core::types::Type::void()))
}
pub fn create_destroy_function(&self, info: &CoroutineInfo) -> ValueRef {
let _frame = info.compute_frame_layout();
llvm_native_core::value::valref(llvm_native_core::value::Value::new(llvm_native_core::types::Type::void()))
}
pub fn allocate_frame(&self, ramp: &ValueRef, frame: &CoroutineFrame) -> ValueRef {
let _ramp_name = ramp.borrow().name.clone();
let _frame_size = frame.size;
llvm_native_core::value::valref(llvm_native_core::value::Value::new(llvm_native_core::types::Type::pointer(0)))
}
}
pub struct CoroutineElider {
pub count_elided: usize,
pub count_considered: usize,
}
impl CoroutineElider {
pub fn new() -> Self {
Self {
count_elided: 0,
count_considered: 0,
}
}
pub fn can_elide(&self, coro: &ValueRef) -> bool {
let info = match CoroutineInfo::analyze(coro) {
Ok(i) => i,
Err(_) => return false,
};
if !info.is_coroutine {
return false;
}
let _frame = info.compute_frame_layout();
info.suspension_points.len() <= 1
}
pub fn elide(&self, coro: &ValueRef) -> ValueRef {
let info = match CoroutineInfo::analyze(coro) {
Ok(i) => i,
Err(_) => return coro.clone(),
};
if !info.is_coroutine {
return coro.clone();
}
let frame = info.compute_frame_layout();
let _ = frame;
coro.clone()
}
}
impl Default for CoroutineElider {
fn default() -> Self {
Self::new()
}
}
impl CoroutineFrame {
pub fn add_field(&mut self, name: &str, ty: &llvm_native_core::types::Type) -> u64 {
let offset = self.size;
let align = ty.size_in_bytes();
let aligned_offset = (offset + align - 1) & !(align - 1);
self.size = aligned_offset + ty.size_in_bytes();
self.num_spills += 1;
aligned_offset
}
pub fn get_field_offset(&self, _name: &str) -> Option<u64> {
None
}
pub fn total_size(&self) -> u64 {
self.size
}
}
#[derive(Debug, Clone)]
pub struct CoroReturn {
pub coro_id: ValueRef,
pub is_final: bool,
pub promise_value: Option<ValueRef>,
}
impl CoroReturn {
pub fn new(coro_id: ValueRef) -> Self {
Self {
coro_id,
is_final: true,
promise_value: None,
}
}
pub fn with_promise(mut self, promise: ValueRef) -> Self {
self.promise_value = Some(promise);
self
}
}
#[derive(Debug, Clone)]
pub struct RetconSuspend {
pub is_retcon: bool,
pub continuation: Option<String>,
pub is_final: bool,
}
impl RetconSuspend {
pub fn new() -> Self {
Self {
is_retcon: false,
continuation: None,
is_final: false,
}
}
pub fn retcon(continuation: &str) -> Self {
Self {
is_retcon: true,
continuation: Some(continuation.into()),
is_final: false,
}
}
pub fn final_suspend() -> Self {
Self {
is_retcon: false,
continuation: None,
is_final: true,
}
}
}
#[derive(Debug, Clone)]
pub struct PromiseLowering {
pub promise_type: String,
pub promise_offset: u64,
pub is_initialized: bool,
pub has_return_value: bool,
pub has_unhandled_exception: bool,
pub has_initial_suspend: bool,
pub has_final_suspend: bool,
pub has_get_return_object: bool,
pub has_yield_value: bool,
}
impl PromiseLowering {
pub fn new(promise_type: &str) -> Self {
Self {
promise_type: promise_type.into(),
promise_offset: 0,
is_initialized: false,
has_return_value: true,
has_unhandled_exception: true,
has_initial_suspend: true,
has_final_suspend: true,
has_get_return_object: true,
has_yield_value: false,
}
}
pub fn set_offset(&mut self, offset: u64) {
self.promise_offset = offset;
self.is_initialized = true;
}
pub fn has_method(&self, method: &str) -> bool {
match method {
"return_value" => self.has_return_value,
"unhandled_exception" => self.has_unhandled_exception,
"initial_suspend" => self.has_initial_suspend,
"final_suspend" => self.has_final_suspend,
"get_return_object" => self.has_get_return_object,
"yield_value" => self.has_yield_value,
_ => false,
}
}
}
impl Default for PromiseLowering {
fn default() -> Self {
Self::new("unknown")
}
}
#[derive(Debug, Clone)]
pub enum FrameSlot {
SpillSlot {
name: String,
offset: u64,
size: u64,
},
CleanupSlot {
index: usize,
offset: u64,
description: String,
},
PromiseSlot { offset: u64, size: u64 },
StateSlot { offset: u64 },
}
impl FrameSlot {
pub fn offset(&self) -> u64 {
match self {
FrameSlot::SpillSlot { offset, .. } => *offset,
FrameSlot::CleanupSlot { offset, .. } => *offset,
FrameSlot::PromiseSlot { offset, .. } => *offset,
FrameSlot::StateSlot { offset } => *offset,
}
}
pub fn size(&self) -> u64 {
match self {
FrameSlot::SpillSlot { size, .. } => *size,
FrameSlot::CleanupSlot { .. } => 8, FrameSlot::PromiseSlot { size, .. } => *size,
FrameSlot::StateSlot { .. } => 4, }
}
}
#[derive(Debug, Clone)]
pub struct FrameLayout {
pub slots: Vec<FrameSlot>,
pub total_size: u64,
pub alignment: u64,
next_offset: u64,
}
impl FrameLayout {
pub fn new() -> Self {
Self {
slots: Vec::new(),
total_size: 0,
alignment: 8,
next_offset: 0,
}
}
pub fn add_spill(&mut self, name: &str, size: u64) -> u64 {
let offset = self.align_offset(size);
self.slots.push(FrameSlot::SpillSlot {
name: name.into(),
offset,
size,
});
self.next_offset = offset + size;
self.total_size = self.next_offset;
offset
}
pub fn add_cleanup(&mut self, index: usize, description: &str) -> u64 {
let offset = self.align_offset(8);
self.slots.push(FrameSlot::CleanupSlot {
index,
offset,
description: description.into(),
});
self.next_offset = offset + 8;
self.total_size = self.next_offset;
offset
}
pub fn add_promise(&mut self, size: u64) -> u64 {
let offset = self.align_offset(size);
self.slots.push(FrameSlot::PromiseSlot { offset, size });
self.next_offset = offset + size;
self.total_size = self.next_offset;
offset
}
pub fn add_state(&mut self) -> u64 {
let offset = self.align_offset(4);
self.slots.push(FrameSlot::StateSlot { offset });
self.next_offset = offset + 4;
self.total_size = self.next_offset;
offset
}
fn align_offset(&self, align: u64) -> u64 {
(self.next_offset + align - 1) & !(align - 1)
}
pub fn spill_count(&self) -> usize {
self.slots
.iter()
.filter(|s| matches!(s, FrameSlot::SpillSlot { .. }))
.count()
}
pub fn cleanup_count(&self) -> usize {
self.slots
.iter()
.filter(|s| matches!(s, FrameSlot::CleanupSlot { .. }))
.count()
}
}
impl Default for FrameLayout {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CoroutineSplitParts {
pub ramp: String,
pub resume: String,
pub destroy: String,
pub success: bool,
pub num_suspension_points: usize,
pub uses_symmetric_transfer: bool,
}
#[derive(Debug, Clone)]
pub struct CoroutineSplitterFull {
pub coroutine_name: String,
pub use_symmetric_transfer: bool,
pub num_suspension_points: usize,
}
impl CoroutineSplitterFull {
pub fn new(coroutine_name: &str) -> Self {
Self {
coroutine_name: coroutine_name.into(),
use_symmetric_transfer: false,
num_suspension_points: 0,
}
}
pub fn split(&mut self, suspension_indices: &[usize]) -> CoroutineSplitParts {
self.num_suspension_points = suspension_indices.len();
CoroutineSplitParts {
ramp: format!("{}.ramp", self.coroutine_name),
resume: format!("{}.resume", self.coroutine_name),
destroy: format!("{}.destroy", self.coroutine_name),
success: true,
num_suspension_points: self.num_suspension_points,
uses_symmetric_transfer: self.use_symmetric_transfer,
}
}
pub fn enable_symmetric_transfer(&mut self) {
self.use_symmetric_transfer = true;
}
pub fn can_symmetric_transfer(&self) -> bool {
self.use_symmetric_transfer
}
}
impl Default for CoroutineSplitterFull {
fn default() -> Self {
Self::new("unnamed")
}
}
#[derive(Debug, Clone)]
pub struct SymmetricTransfer {
pub enabled: bool,
pub tail_call: bool,
pub transfer_count: usize,
pub max_chain_length: usize,
chain_depth: usize,
}
impl SymmetricTransfer {
pub fn new() -> Self {
Self {
enabled: false,
tail_call: true,
transfer_count: 0,
max_chain_length: 64,
chain_depth: 0,
}
}
pub fn try_transfer(&mut self) -> bool {
if !self.enabled || self.chain_depth >= self.max_chain_length {
return false;
}
self.transfer_count += 1;
self.chain_depth += 1;
true
}
pub fn reset_chain(&mut self) {
self.chain_depth = 0;
}
}
impl Default for SymmetricTransfer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct HeapElisionAnalysis {
pub can_elide: bool,
pub elision_blocker: Option<String>,
pub escapes_scope: bool,
pub frame_too_large: bool,
pub max_stack_frame: u64,
pub elided_count: usize,
pub heap_count: usize,
}
impl HeapElisionAnalysis {
pub fn new() -> Self {
Self {
can_elide: true,
elision_blocker: None,
escapes_scope: false,
frame_too_large: false,
max_stack_frame: 65536, elided_count: 0,
heap_count: 0,
}
}
pub fn analyze(&mut self, frame_size: u64, escapes: bool) -> bool {
self.escapes_scope = escapes;
self.frame_too_large = frame_size > self.max_stack_frame;
if escapes {
self.can_elide = false;
self.elision_blocker = Some("coroutine escapes its scope".into());
} else if frame_size > self.max_stack_frame {
self.can_elide = false;
self.elision_blocker = Some(format!(
"frame size {} exceeds max stack frame {}",
frame_size, self.max_stack_frame
));
} else {
self.can_elide = true;
self.elision_blocker = None;
}
if self.can_elide {
self.elided_count += 1;
} else {
self.heap_count += 1;
}
self.can_elide
}
pub fn stats(&self) -> (usize, usize) {
(self.elided_count, self.heap_count)
}
}
impl Default for HeapElisionAnalysis {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CoroutineCleanup {
pub actions: Vec<CleanupAction>,
pub cleaned_up: bool,
}
#[derive(Debug, Clone)]
pub enum CleanupAction {
DestroyObject { offset: u64, type_name: String },
FreeFrame { frame_ptr: String },
CustomCleanup { description: String },
}
impl CoroutineCleanup {
pub fn new() -> Self {
Self {
actions: Vec::new(),
cleaned_up: false,
}
}
pub fn add_action(&mut self, action: CleanupAction) {
self.actions.push(action);
}
pub fn destroy_sequence(&self) -> Vec<&CleanupAction> {
self.actions.iter().rev().collect()
}
pub fn mark_cleaned_up(&mut self) {
self.cleaned_up = true;
}
pub fn action_count(&self) -> usize {
self.actions.len()
}
}
impl Default for CoroutineCleanup {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use llvm_native_core::basic_block::new_basic_block;
use llvm_native_core::function::new_function;
use llvm_native_core::instruction;
use llvm_native_core::types::Type;
fn build_simple_func(name: &str) -> ValueRef {
let func = new_function(name, Type::void(), &[]);
let entry = new_basic_block("entry");
entry.borrow_mut().push_operand(instruction::ret_void());
func.borrow_mut().push_operand(entry.clone());
func
}
#[test]
fn test_intrinsic_names() {
assert_eq!(CoroutineIntrinsic::CoroId.name(), "llvm.coro.id");
assert_eq!(CoroutineIntrinsic::CoroBegin.name(), "llvm.coro.begin");
assert_eq!(CoroutineIntrinsic::CoroSuspend.name(), "llvm.coro.suspend");
assert_eq!(CoroutineIntrinsic::CoroEnd.name(), "llvm.coro.end");
}
#[test]
fn test_analyzer_create() {
let analyzer = CoroutineAnalyzer::new();
let func = build_simple_func("test");
let analysis = analyzer.analyze(&func);
assert!(!analysis.is_coroutine);
}
#[test]
fn test_analyzer_non_coroutine() {
let analyzer = CoroutineAnalyzer::new();
let func = build_simple_func("regular_fn");
let analysis = analyzer.analyze(&func);
assert!(!analysis.is_coroutine);
assert_eq!(analysis.intrinsic_count, 0);
}
#[test]
fn test_lowering_create() {
let lowerer = CoroutineLowering::new();
assert_eq!(lowerer.default_frame_size, 256);
}
#[test]
fn test_lowering_non_coroutine() {
let lowerer = CoroutineLowering::new();
let func = build_simple_func("not_coro");
let result = lowerer.lower(&func);
assert!(!result.lowered);
}
#[test]
fn test_splitter_create() {
let splitter = CoroutineSplitter::new();
let func = build_simple_func("split_test");
let result = splitter.split(&func);
assert!(result.split);
assert!(result.ramp_name.contains("ramp"));
assert!(result.resume_name.contains("resume"));
assert!(result.destroy_name.contains("destroy"));
}
#[test]
fn test_process_coroutines_empty() {
let m = llvm_native_core::module::Module::new("empty_coro");
let stats = process_coroutines(&m);
assert_eq!(stats.coroutines_found, 0);
}
#[test]
fn test_process_coroutines_simple_module() {
let mut m = llvm_native_core::module::Module::new("coro_mod");
m.add_function(build_simple_func("f1"));
m.add_function(build_simple_func("f2"));
let stats = process_coroutines(&m);
assert_eq!(stats.coroutines_found, 0);
}
#[test]
fn test_coroutine_frame_default() {
let frame = CoroutineFrame {
size: 256,
alignment: 16,
is_heap_allocated: true,
coro_id: None,
promise: None,
suspension_points: vec![],
state_field_offset: 0,
promise_field_offset: 8,
num_spills: 0,
};
assert!(!frame.is_heap_allocated || true);
assert_eq!(frame.size, 256);
}
#[test]
fn test_suspension_point_create() {
let sp = SuspensionPoint {
index: 0,
suspend_inst: build_simple_func("suspend"),
is_final: false,
resume_state: 1,
};
assert_eq!(sp.index, 0);
assert!(!sp.is_final);
}
#[test]
fn test_coroutine_state_values() {
assert_eq!(CoroutineState::Initial, CoroutineState::Initial);
assert_ne!(CoroutineState::Running, CoroutineState::Final);
}
#[test]
fn test_coroutine_full_pipeline() {
let func = build_simple_func("pipeline_coro");
let analyzer = CoroutineAnalyzer::new();
let analysis = analyzer.analyze(&func);
let lowerer = CoroutineLowering::new();
let result = lowerer.lower(&func);
let splitter = CoroutineSplitter::new();
let split = splitter.split(&func);
assert!(!analysis.is_coroutine);
assert!(!result.lowered);
assert!(split.split);
}
#[test]
fn test_coroutine_stats_structure() {
let stats = CoroutineStats {
coroutines_found: 5,
coroutines_lowered: 3,
total_suspension_points: 12,
total_frame_size: 1024,
heap_allocations_elided: 2,
};
assert_eq!(stats.coroutines_found, 5);
assert!(stats.coroutines_lowered <= stats.coroutines_found);
}
#[test]
fn test_coroutine_lowering_result() {
let result = CoroutineLoweringResult {
lowered: true,
frame_size: 512,
num_suspension_points: 3,
heap_elided: false,
};
assert!(result.lowered);
assert_eq!(result.num_suspension_points, 3);
}
#[test]
fn test_coroutine_info_analyze_non_coroutine() {
let func = build_simple_func("not_coro");
let info = CoroutineInfo::analyze(&func);
assert!(info.is_ok());
let info = info.unwrap();
assert!(!info.is_coroutine);
}
#[test]
fn test_coroutine_info_find_suspension_points() {
let func = build_simple_func("susp_test");
let points = CoroutineInfo::find_suspension_points(&func);
assert!(points.is_empty());
}
#[test]
fn test_coroutine_info_compute_frame_layout() {
let func = build_simple_func("frame_test");
let info = CoroutineInfo::analyze(&func).unwrap();
let frame = info.compute_frame_layout();
assert!(frame.size >= 16);
assert_eq!(frame.state_field_offset, 0);
assert_eq!(frame.promise_field_offset, 8);
}
#[test]
fn test_coroutine_splitter_split_full() {
let splitter = CoroutineSplitter::new();
let func = build_simple_func("split_full");
let result = splitter.split_full(&func);
assert!(result.is_err());
}
#[test]
fn test_coroutine_splitter_create_ramp() {
let splitter = CoroutineSplitter::new();
let func = build_simple_func("ramp_test");
let info = CoroutineInfo::analyze(&func).unwrap();
let _ramp = splitter.create_ramp_function(&info);
}
#[test]
fn test_coroutine_splitter_create_resume() {
let splitter = CoroutineSplitter::new();
let func = build_simple_func("resume_test");
let info = CoroutineInfo::analyze(&func).unwrap();
let _resume = splitter.create_resume_function(&info);
}
#[test]
fn test_coroutine_splitter_create_destroy() {
let splitter = CoroutineSplitter::new();
let func = build_simple_func("destroy_test");
let info = CoroutineInfo::analyze(&func).unwrap();
let _destroy = splitter.create_destroy_function(&info);
}
#[test]
fn test_coroutine_elider_create() {
let elide = CoroutineElider::new();
assert_eq!(elide.count_elided, 0);
assert_eq!(elide.count_considered, 0);
}
#[test]
fn test_coroutine_elider_can_elide_non_coro() {
let elider = CoroutineElider::new();
let func = build_simple_func("non_coro");
assert!(!elider.can_elide(&func));
}
#[test]
fn test_coroutine_elider_elide_non_coro() {
let elider = CoroutineElider::new();
let func = build_simple_func("non_coro");
let result = elider.elide(&func);
let _ = result;
}
#[test]
fn test_coroutine_frame_add_field() {
let mut frame = CoroutineFrame {
size: 16,
alignment: 16,
is_heap_allocated: false,
coro_id: None,
promise: None,
suspension_points: vec![],
state_field_offset: 0,
promise_field_offset: 8,
num_spills: 0,
};
let offset = frame.add_field("var1", &Type::i32());
assert!(offset >= 16); assert_eq!(frame.num_spills, 1);
}
#[test]
fn test_coroutine_frame_total_size() {
let frame = CoroutineFrame {
size: 256,
alignment: 16,
is_heap_allocated: true,
coro_id: None,
promise: None,
suspension_points: vec![],
state_field_offset: 0,
promise_field_offset: 8,
num_spills: 3,
};
assert_eq!(frame.total_size(), 256);
}
#[test]
fn test_coroutine_frame_add_multiple_fields() {
let mut frame = CoroutineFrame {
size: 16,
alignment: 16,
is_heap_allocated: false,
coro_id: None,
promise: None,
suspension_points: vec![],
state_field_offset: 0,
promise_field_offset: 8,
num_spills: 0,
};
let off1 = frame.add_field("a", &Type::i64());
let off2 = frame.add_field("b", &Type::i32());
assert!(off2 > off1);
assert!(frame.size >= 16 + 8 + 4); assert_eq!(frame.num_spills, 2);
}
#[test]
fn test_coroutine_info_states() {
let func = build_simple_func("state_test");
let info = CoroutineInfo::analyze(&func).unwrap();
assert!(!info.is_coroutine);
assert_eq!(info.current_state, 0);
}
}