1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
//! Management of two kernel personalities, one for SIMD-enabled code, and one for regular code. 
//! 
//! This crate is responsible for creating and managing multiple `CrateNamespace`s so that Theseus
//! can run two instances of code side-by-side, like library OS-style personalities. 
//! This crate itself is part of the regular non-SIMD world, not the SIMD world. 
//! 
//! There are two considerations here to ensure correctness by saving/restoring SIMD registers: 
//! how to deal with it for context switching, and also for interrupt handling.
//! Both of these policies are determined per-core as follows:
//! 
//! # Context Switching 
//! * If there is one or zero SIMD-enabled `Task`s running on a given core, 
//!   then all `Task`s running on that core can use the standard, 
//!   non-SIMD-enabled context switching routine. 
//!   This is because there is no need to save and restore SIMD registers (e.g., xmm),
//!   as only a single `Task` will ever use them. 
//! * If there are multiple SIMD-enabled `Task`s running on a given core,
//!   then all of the SIMD `Task`s on that core must use the SIMD-enabled context switching routine,
//!   which is found in the `context_switch_sse` crate. 
//! 
//! Note that there is no danger in forcing all SIMD Tasks to always use the SIMD-enabled context switching routine,
//! it will always be correct to do so.
//! Each Task can have its own Context and context_switch routine, based on whether or not it uses SIMD instructions,
//! and that can be determined statically and independently for each task, without considering which other tasks are running. 
//! We don't really need personalities for that, since the `context_switch` routines are self-contained.  
//! However, that static policy misses out on the performance optimization of 
//! not having to save/restore SIMD registers when only a single SIMD Task is running on a given core.
//! 
//! # Interrupt Handling
//! * If interrupt handlers are only ever compiled for the regular world, i.e., 
//!   no interrupt handlers exist that are compiled to use SIMD instructions,
//!   then we do not have to save/restore SIMD registers on an interrupt because
//!   we're guaranteed that no interrupt handling code can ever use (overwrite) SIMD registers. 
//!   Thus, even if there are some SIMD enabled tasks running on a given core, an interrupt handler need not save
//!   those SIMD registers if it cannot possibly ever touch them. 
//! 
//! * If interrupt handlers _are_ compiled for the SIMD world and use SIMD instructions in the handler 
//!   (or any function accessible from the interrupt handler), then they must (and obviously will) save SIMD registers.
//!   In fact, I don't believe it's possible to compile an interrupt handler that uses SIMD instructions but doesn't save SIMD registers
//!   (at least while we're stil using the special x86 interrupt calling convention to have LLVM do it for us).
//!   
//! Thus, the best option is just to require that any SIMD-enabled interrupt handlers must save all SIMD registers, 
//! which is a rule determined completely independently of which tasks are running on that core. 
//! In general, this is a good rule, because it's poor design to have an interrupt handler do a lot of work,
//! such as processing data in a way that would need SIMD instructions. 
//! Instead, those processing stages should be moved out of the interrupt handler and into a separate Task elsewhere,
//! i.e., a classic bottom-half/top-half design.
//!


#![no_std]

// NOTE: the `cfg_if` macro makes the entire file dependent upon the `simd_personality` config.
#[macro_use] extern crate cfg_if;
cfg_if! { if #[cfg(simd_personality)] {


/* 
 * NOTE: now, we're using the compiler_builtins crate that is built by cargo's build-std feature by default, but we can switch back
 * to this one if needed since it does export different symbols based on Cargo.toml feature choices.
// This crate is required for the SIMD environment,
// so we can resolve symbols that the core lib requires. 
#[cfg(target_feature = "sse2")]
extern crate compiler_builtins as _compiler_builtins; 
*/


#[macro_use] extern crate log;
#[macro_use] extern crate alloc;
extern crate memory;
extern crate mod_mgmt;
extern crate task;
extern crate spawn;
extern crate cpu;
extern crate fs_node;


use alloc::{
	string::String,
	sync::Arc,
};
use mod_mgmt::{CrateNamespace, CrateType, NamespaceDir, get_initial_kernel_namespace, get_namespaces_directory};
use task::SimdExt;


/// Initializes a new SIMD personality based on the provided `simd_ext` level of given SIMD extensions, e.g., SSE, AVX.
pub fn setup_simd_personality(simd_ext: SimdExt) -> Result<(), &'static str> {
	match internal_setup_simd_personality(simd_ext) {
		Ok(o) => {
			debug!("SIMD personality setup completed successfully.");
			Ok(o)
		}
		Err(e) => {
			error!("Error setting up SIMD personality: {}", e); 
			Err(e)
		}
	}
}


fn internal_setup_simd_personality(simd_ext: SimdExt) -> Result<(), &'static str> {
	let namespace_prefix = match simd_ext {
		SimdExt::AVX => "avx",
		SimdExt::SSE => "sse",
		SimdExt::None => return Err("Cannot create a new SIMD personality with SimdExt::None!"),
	};

	let kernel_mmi_ref = memory::get_kernel_mmi_ref().ok_or_else(|| "couldn't get kernel mmi")?;
	let backup_namespace = get_initial_kernel_namespace().ok_or("initial kernel crate namespace wasn't yet initialized")?;

	// The `mod_mgmt::init()` function should have initialized the following directories, 
	// for example, if 'sse' was the prefix used to build the SSE versions of each crate:
	//     .../namespaces/sse_kernel
	//     .../namespaces/sse_application
	let namespaces_dir = get_namespaces_directory().ok_or("top-level namespaces directory wasn't yet initialized")?;
	
	// Create the SIMD kernel namespace
	let simd_kernel_namespace = {
		let namespace_name = format!("{}{}", namespace_prefix, CrateType::Kernel.default_namespace_name());
		let dir = namespaces_dir.lock().get_dir(&namespace_name).ok_or("couldn't find SIMD kernel namespace directory at given path")?;
		Arc::new(CrateNamespace::new(
			String::from(namespace_name), 
			NamespaceDir::new(dir),
			None,
		))
	};

	// Then create the SIMD application namespace that is recursively backed by the simd_kernel_namespace
	let mut simd_app_namespace = {
		let namespace_name = format!("{}{}", namespace_prefix, CrateType::Application.default_namespace_name());
		let dir = namespaces_dir.lock().get_dir(&namespace_name).ok_or("couldn't find SIMD application namespace directory at given path")?;
		CrateNamespace::new(
			String::from(namespace_name), 
			NamespaceDir::new(dir),
			Some(Arc::clone(&simd_kernel_namespace)),
		)
	};

	// Load things that are specific (private) to the SIMD world, like core library and compiler builtins
	let (compiler_builtins_simd, _ns) = CrateNamespace::get_crate_object_file_starting_with(&simd_kernel_namespace, "compiler_builtins-")
		.ok_or_else(|| "couldn't find a single 'compiler_builtins' object file in simd_personality")?;
	let (core_lib_simd, _ns) = CrateNamespace::get_crate_object_file_starting_with(&simd_kernel_namespace, "core-")
		.ok_or_else(|| "couldn't find a single 'core' object file in simd_personality")?;
	let crate_files = [compiler_builtins_simd, core_lib_simd];
	simd_kernel_namespace.load_crates(crate_files.iter(), Some(backup_namespace), kernel_mmi_ref, false)?;
	

	// load the actual crate that we want to run in the simd namespace, "simd_test"
	let (simd_test_file, _ns) = simd_app_namespace.method_get_crate_object_file_starting_with("simd_test-")
		.ok_or_else(|| "couldn't find a single 'simd_test' object file in simd_personality")?;
	simd_app_namespace.enable_fuzzy_symbol_matching();
	simd_app_namespace.load_crate(&simd_test_file, Some(backup_namespace), kernel_mmi_ref, false)?;
	simd_app_namespace.disable_fuzzy_symbol_matching();


	let this_core = cpu::current_cpu();
	
	type SimdTestFunc = fn(());
	let section_ref1 = simd_app_namespace.get_symbol_starting_with("simd_test::test1::")
		.upgrade()
		.ok_or("no single symbol matching \"simd_test::test1\"")?;
	let func1: &SimdTestFunc = unsafe { section_ref1.as_func() }?;
	let _task1 = spawn::new_task_builder(*func1, ())
		.name(format!("simd_test_1-{}", simd_app_namespace.name()))
		.pin_on_cpu(this_core)
		.simd(simd_ext)
		.spawn()?;
	debug!("finished spawning simd_test::test1 task");


	let section_ref2 = simd_app_namespace.get_symbol_starting_with("simd_test::test2::")
		.upgrade()
		.ok_or("no single symbol matching \"simd_test::test2\"")?;
	let func2: &SimdTestFunc = unsafe { section_ref2.as_func() }?;
	let _task2 = spawn::new_task_builder(*func2, ())
		.name(format!("simd_test_2-{}", simd_app_namespace.name()))
		.pin_on_cpu(this_core)
		.simd(simd_ext)
		.spawn()?;
	debug!("finished spawning simd_test::test2 task");


	let section_ref3 = simd_app_namespace.get_symbol_starting_with("simd_test::test_short::")
		.upgrade()
		.ok_or("no single symbol matching \"simd_test::test_short\"")?;
	let func3: &SimdTestFunc = unsafe { section_ref3.as_func() }?;
	let _task3 = spawn::new_task_builder(*func3, ())
		.name(format!("simd_test_short-{}", simd_app_namespace.name()))
		.pin_on_cpu(this_core)
		.simd(simd_ext)
		.spawn()?;
	debug!("finished spawning simd_test::test_short task");


	// we can't return here because the mapped pages that contain
	// the simd_test functions being run must not be dropped 
	// until the threads are completed.
	// TODO FIXME: check for this somehow in the thread spawn code, perhaps by giving the new thread ownership of the MappedPages,
	//             just like we do for application Tasks

	loop { core::hint::spin_loop() }
	
	// _task1.join()?;
	// _task2.join()?;
	// _task3.join()?;
	// Ok(())
}
		
}} // end of cfg_if block