pub mod backend;
pub mod builder;
pub mod debug;
pub mod ir;
pub mod lower;
pub mod runtime;
pub use backend::{Backend, CodegenError, CodegenResult, Target};
pub use builder::*;
pub use ir::*;
pub use lower::*;
use std::sync::Arc;
use crate::ast;
use crate::types::TypeContext;
pub struct CodeGenerator<'ctx> {
ctx: &'ctx TypeContext,
target: Target,
mir: Option<MirModule>,
source: Option<Arc<str>>,
pub reshade: bool,
}
impl<'ctx> CodeGenerator<'ctx> {
pub fn new(ctx: &'ctx TypeContext, target: Target) -> Self {
Self {
ctx,
target,
mir: None,
source: None,
reshade: false,
}
}
pub fn with_source(ctx: &'ctx TypeContext, target: Target, source: Arc<str>) -> Self {
Self {
ctx,
target,
mir: None,
source: Some(source),
reshade: false,
}
}
pub fn generate(&mut self, module: &ast::Module) -> CodegenResult<GeneratedCode> {
let lowerer = if let Some(ref source) = self.source {
MirLowerer::with_source(self.ctx, source.clone())
} else {
MirLowerer::new(self.ctx)
};
let mir = lowerer.lower_module(module)?;
self.mir = Some(mir);
let mir = self.mir.as_ref().unwrap();
match self.target {
Target::C => {
let mut backend = backend::c::CBackend::new();
backend.generate(mir)
}
Target::X86_64 => {
let mut backend = backend::x86_64::X86_64Backend::new();
backend.generate(mir)
}
Target::Arm64 => {
let mut backend = backend::arm64::Arm64Backend::new();
backend.generate(mir)
}
Target::Wasm => {
let mut backend = backend::wasm::WasmBackend::new();
backend.generate(mir)
}
Target::SpirV => {
let mut backend = backend::spirv::SpirvBackend::new();
backend.generate(mir)
}
Target::LlvmIr => {
let mut backend = backend::llvm::LlvmBackend::new();
backend.generate(mir)
}
Target::Hlsl => {
let mut backend = backend::hlsl::HlslBackend::new();
let hlsl_code = if self.reshade {
backend.generate_reshade(mir)?
} else {
backend.generate(mir)?
};
Ok(GeneratedCode::new(
OutputFormat::Hlsl,
hlsl_code.into_bytes(),
))
}
Target::Glsl => {
let mut backend = backend::glsl::GlslBackend::new();
let glsl_code = backend.generate(mir)?;
Ok(GeneratedCode::new(
OutputFormat::Glsl,
glsl_code.into_bytes(),
))
}
}
}
pub fn mir(&self) -> Option<&MirModule> {
self.mir.as_ref()
}
}
#[derive(Debug)]
pub struct GeneratedCode {
pub format: OutputFormat,
pub data: Vec<u8>,
pub debug_info: Option<DebugInfo>,
}
impl GeneratedCode {
pub fn new(format: OutputFormat, data: Vec<u8>) -> Self {
Self {
format,
data,
debug_info: None,
}
}
pub fn with_debug_info(mut self, debug_info: DebugInfo) -> Self {
self.debug_info = Some(debug_info);
self
}
pub fn as_string(&self) -> Option<String> {
match self.format {
OutputFormat::CSource
| OutputFormat::Assembly
| OutputFormat::Wat
| OutputFormat::LlvmIr
| OutputFormat::Hlsl
| OutputFormat::Glsl => String::from_utf8(self.data.clone()).ok(),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
CSource,
Assembly,
Object,
Executable,
Wasm,
Wat,
SpirV,
LlvmIr,
Hlsl,
Glsl,
}
#[derive(Debug, Clone)]
pub struct DebugInfo {
pub source_maps: Vec<SourceMap>,
}
#[derive(Debug, Clone)]
pub struct SourceMap {
pub generated_offset: usize,
pub source_file: String,
pub line: u32,
pub column: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generated_code_new() {
let code = GeneratedCode::new(OutputFormat::CSource, b"int main() { return 0; }".to_vec());
assert_eq!(code.format, OutputFormat::CSource);
assert_eq!(code.data, b"int main() { return 0; }");
assert!(code.debug_info.is_none());
}
#[test]
fn test_generated_code_as_string() {
let code = GeneratedCode::new(OutputFormat::CSource, b"int main() { return 0; }".to_vec());
assert_eq!(
code.as_string(),
Some("int main() { return 0; }".to_string())
);
}
#[test]
fn test_generated_code_as_string_assembly() {
let code = GeneratedCode::new(OutputFormat::Assembly, b"mov rax, 42\nret".to_vec());
assert_eq!(code.as_string(), Some("mov rax, 42\nret".to_string()));
}
#[test]
fn test_generated_code_as_string_wat() {
let code = GeneratedCode::new(
OutputFormat::Wat,
b"(module (func (export \"main\")))".to_vec(),
);
assert_eq!(
code.as_string(),
Some("(module (func (export \"main\")))".to_string())
);
}
#[test]
fn test_generated_code_as_string_binary_formats() {
let wasm = GeneratedCode::new(OutputFormat::Wasm, vec![0x00, 0x61, 0x73, 0x6d]);
assert!(wasm.as_string().is_none());
let object = GeneratedCode::new(OutputFormat::Object, vec![0x7f, 0x45, 0x4c, 0x46]);
assert!(object.as_string().is_none());
let executable = GeneratedCode::new(OutputFormat::Executable, vec![0x4d, 0x5a]);
assert!(executable.as_string().is_none());
let spirv = GeneratedCode::new(OutputFormat::SpirV, vec![0x03, 0x02, 0x23, 0x07]);
assert!(spirv.as_string().is_none());
}
#[test]
fn test_generated_code_as_string_invalid_utf8() {
let code = GeneratedCode::new(
OutputFormat::CSource,
vec![0xff, 0xfe, 0x00, 0x01], );
assert!(code.as_string().is_none());
}
#[test]
fn test_generated_code_with_debug_info() {
let debug_info = DebugInfo {
source_maps: vec![SourceMap {
generated_offset: 0,
source_file: "main.qta".to_string(),
line: 1,
column: 0,
}],
};
let code = GeneratedCode::new(OutputFormat::CSource, b"int main() {}".to_vec())
.with_debug_info(debug_info);
assert!(code.debug_info.is_some());
let info = code.debug_info.unwrap();
assert_eq!(info.source_maps.len(), 1);
assert_eq!(info.source_maps[0].source_file, "main.qta");
}
#[test]
fn test_generated_code_empty() {
let code = GeneratedCode::new(OutputFormat::CSource, vec![]);
assert!(code.data.is_empty());
assert_eq!(code.as_string(), Some(String::new()));
}
#[test]
fn test_output_format_equality() {
assert_eq!(OutputFormat::CSource, OutputFormat::CSource);
assert_eq!(OutputFormat::Assembly, OutputFormat::Assembly);
assert_eq!(OutputFormat::Object, OutputFormat::Object);
assert_eq!(OutputFormat::Executable, OutputFormat::Executable);
assert_eq!(OutputFormat::Wasm, OutputFormat::Wasm);
assert_eq!(OutputFormat::Wat, OutputFormat::Wat);
assert_eq!(OutputFormat::SpirV, OutputFormat::SpirV);
}
#[test]
fn test_output_format_inequality() {
assert_ne!(OutputFormat::CSource, OutputFormat::Assembly);
assert_ne!(OutputFormat::Wasm, OutputFormat::Wat);
assert_ne!(OutputFormat::Object, OutputFormat::Executable);
}
#[test]
fn test_output_format_clone() {
let format = OutputFormat::CSource;
let cloned = format.clone();
assert_eq!(format, cloned);
}
#[test]
fn test_output_format_copy() {
let format = OutputFormat::Assembly;
let copied = format;
assert_eq!(format, copied); }
#[test]
fn test_output_format_debug() {
assert_eq!(format!("{:?}", OutputFormat::CSource), "CSource");
assert_eq!(format!("{:?}", OutputFormat::Assembly), "Assembly");
assert_eq!(format!("{:?}", OutputFormat::Object), "Object");
assert_eq!(format!("{:?}", OutputFormat::Executable), "Executable");
assert_eq!(format!("{:?}", OutputFormat::Wasm), "Wasm");
assert_eq!(format!("{:?}", OutputFormat::Wat), "Wat");
assert_eq!(format!("{:?}", OutputFormat::SpirV), "SpirV");
}
#[test]
fn test_debug_info_new() {
let debug_info = DebugInfo {
source_maps: vec![],
};
assert!(debug_info.source_maps.is_empty());
}
#[test]
fn test_debug_info_with_source_maps() {
let debug_info = DebugInfo {
source_maps: vec![
SourceMap {
generated_offset: 0,
source_file: "lib.qta".to_string(),
line: 1,
column: 0,
},
SourceMap {
generated_offset: 100,
source_file: "lib.qta".to_string(),
line: 10,
column: 4,
},
SourceMap {
generated_offset: 200,
source_file: "util.qta".to_string(),
line: 5,
column: 8,
},
],
};
assert_eq!(debug_info.source_maps.len(), 3);
}
#[test]
fn test_debug_info_clone() {
let debug_info = DebugInfo {
source_maps: vec![SourceMap {
generated_offset: 42,
source_file: "test.qta".to_string(),
line: 5,
column: 10,
}],
};
let cloned = debug_info.clone();
assert_eq!(cloned.source_maps.len(), 1);
assert_eq!(cloned.source_maps[0].generated_offset, 42);
}
#[test]
fn test_source_map_new() {
let map = SourceMap {
generated_offset: 256,
source_file: "main.qta".to_string(),
line: 42,
column: 8,
};
assert_eq!(map.generated_offset, 256);
assert_eq!(map.source_file, "main.qta");
assert_eq!(map.line, 42);
assert_eq!(map.column, 8);
}
#[test]
fn test_source_map_clone() {
let map = SourceMap {
generated_offset: 100,
source_file: "test.qta".to_string(),
line: 1,
column: 0,
};
let cloned = map.clone();
assert_eq!(cloned.generated_offset, map.generated_offset);
assert_eq!(cloned.source_file, map.source_file);
assert_eq!(cloned.line, map.line);
assert_eq!(cloned.column, map.column);
}
#[test]
fn test_source_map_debug() {
let map = SourceMap {
generated_offset: 0,
source_file: "x.qta".to_string(),
line: 1,
column: 0,
};
let debug = format!("{:?}", map);
assert!(debug.contains("SourceMap"));
assert!(debug.contains("generated_offset"));
assert!(debug.contains("x.qta"));
}
#[test]
fn test_code_generator_new() {
let ctx = TypeContext::new();
let codegen = CodeGenerator::new(&ctx, Target::C);
assert_eq!(codegen.target, Target::C);
assert!(codegen.mir.is_none());
}
#[test]
fn test_code_generator_new_all_targets() {
let ctx = TypeContext::new();
let cg_c = CodeGenerator::new(&ctx, Target::C);
assert_eq!(cg_c.target, Target::C);
let cg_x86 = CodeGenerator::new(&ctx, Target::X86_64);
assert_eq!(cg_x86.target, Target::X86_64);
let cg_arm = CodeGenerator::new(&ctx, Target::Arm64);
assert_eq!(cg_arm.target, Target::Arm64);
let cg_wasm = CodeGenerator::new(&ctx, Target::Wasm);
assert_eq!(cg_wasm.target, Target::Wasm);
let cg_spirv = CodeGenerator::new(&ctx, Target::SpirV);
assert_eq!(cg_spirv.target, Target::SpirV);
}
#[test]
fn test_code_generator_mir_initially_none() {
let ctx = TypeContext::new();
let codegen = CodeGenerator::new(&ctx, Target::C);
assert!(codegen.mir().is_none());
}
#[test]
fn test_target_variants() {
let targets = [
Target::C,
Target::X86_64,
Target::Arm64,
Target::Wasm,
Target::SpirV,
];
assert_eq!(targets.len(), 5);
}
#[test]
fn test_target_equality() {
assert_eq!(Target::C, Target::C);
assert_eq!(Target::X86_64, Target::X86_64);
assert_ne!(Target::C, Target::X86_64);
assert_ne!(Target::Wasm, Target::SpirV);
}
#[test]
fn test_full_pipeline_empty_module() {
let ctx = TypeContext::new();
let mut codegen = CodeGenerator::new(&ctx, Target::C);
let module = ast::Module {
attrs: vec![],
items: vec![],
span: ast::Span::dummy(),
};
let result = codegen.generate(&module);
assert!(result.is_ok());
let generated = result.unwrap();
assert_eq!(generated.format, OutputFormat::CSource);
assert!(codegen.mir().is_some());
}
#[test]
fn test_full_pipeline_c_backend() {
let ctx = TypeContext::new();
let mut codegen = CodeGenerator::new(&ctx, Target::C);
let module = ast::Module {
attrs: vec![],
items: vec![],
span: ast::Span::dummy(),
};
let result = codegen.generate(&module);
assert!(result.is_ok());
let generated = result.unwrap();
assert_eq!(generated.format, OutputFormat::CSource);
assert!(generated.as_string().is_some());
}
#[test]
fn test_full_pipeline_x86_64_backend() {
let ctx = TypeContext::new();
let mut codegen = CodeGenerator::new(&ctx, Target::X86_64);
let module = ast::Module {
attrs: vec![],
items: vec![],
span: ast::Span::dummy(),
};
let result = codegen.generate(&module);
assert!(result.is_ok());
let generated = result.unwrap();
assert_eq!(generated.format, OutputFormat::Assembly);
assert!(generated.as_string().is_some());
}
#[test]
fn test_full_pipeline_arm64_backend() {
let ctx = TypeContext::new();
let mut codegen = CodeGenerator::new(&ctx, Target::Arm64);
let module = ast::Module {
attrs: vec![],
items: vec![],
span: ast::Span::dummy(),
};
let result = codegen.generate(&module);
assert!(result.is_ok());
let generated = result.unwrap();
assert_eq!(generated.format, OutputFormat::Assembly);
}
#[test]
fn test_full_pipeline_wasm_backend() {
let ctx = TypeContext::new();
let mut codegen = CodeGenerator::new(&ctx, Target::Wasm);
let module = ast::Module {
attrs: vec![],
items: vec![],
span: ast::Span::dummy(),
};
let result = codegen.generate(&module);
assert!(result.is_ok());
let generated = result.unwrap();
assert_eq!(generated.format, OutputFormat::Wat);
assert!(generated.as_string().is_some());
}
#[test]
fn test_full_pipeline_spirv_backend() {
let ctx = TypeContext::new();
let mut codegen = CodeGenerator::new(&ctx, Target::SpirV);
let module = ast::Module {
attrs: vec![],
items: vec![],
span: ast::Span::dummy(),
};
let result = codegen.generate(&module);
assert!(result.is_ok());
let generated = result.unwrap();
assert_eq!(generated.format, OutputFormat::SpirV);
assert!(generated.as_string().is_none());
}
#[test]
fn test_mir_accessible_after_generation() {
let ctx = TypeContext::new();
let mut codegen = CodeGenerator::new(&ctx, Target::C);
assert!(codegen.mir().is_none());
let module = ast::Module {
attrs: vec![],
items: vec![],
span: ast::Span::dummy(),
};
let _ = codegen.generate(&module);
let mir = codegen.mir();
assert!(mir.is_some());
}
#[test]
fn test_multiple_generations_overwrite_mir() {
let ctx = TypeContext::new();
let mut codegen = CodeGenerator::new(&ctx, Target::C);
let module1 = ast::Module {
attrs: vec![],
items: vec![],
span: ast::Span::dummy(),
};
let _ = codegen.generate(&module1);
assert!(codegen.mir().is_some());
let module2 = ast::Module {
attrs: vec![],
items: vec![],
span: ast::Span::dummy(),
};
let _ = codegen.generate(&module2);
assert!(codegen.mir().is_some());
}
}