From 5f93b7c9c2dbae49ecedb686261539168ade95a9 Mon Sep 17 00:00:00 2001 From: sofia Date: Tue, 24 Jun 2025 23:10:44 +0300 Subject: [PATCH] Add rudamentary LLVM lib stuff, make a fully compiling executable --- .gitignore | 4 +- Cargo.lock | 10 +- Cargo.toml | 3 +- libtest.sh | 11 ++ main.cpp | 7 + reid-llvm-lib/Cargo.toml | 12 ++ reid-llvm-lib/examples/libtest.rs | 25 +++ reid-llvm-lib/src/lib.rs | 319 ++++++++++++++++++++++++++++++ reid/src/codegen/llvm.rs | 6 +- 9 files changed, 393 insertions(+), 4 deletions(-) create mode 100755 libtest.sh create mode 100644 main.cpp create mode 100644 reid-llvm-lib/Cargo.toml create mode 100644 reid-llvm-lib/examples/libtest.rs create mode 100644 reid-llvm-lib/src/lib.rs diff --git a/.gitignore b/.gitignore index a179d95..a73c16d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ src/old_llvm /target /.vscode -.env \ No newline at end of file +.env +hello.* +main \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c638dd3..0de9c85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -103,6 +103,14 @@ dependencies = [ "thiserror", ] +[[package]] +name = "reid-lib" +version = "0.1.0" +dependencies = [ + "llvm-sys", + "thiserror", +] + [[package]] name = "semver" version = "1.0.18" diff --git a/Cargo.toml b/Cargo.toml index e1b07fd..640a093 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] members = [ - "reid" + "reid", + "reid-llvm-lib" ] \ No newline at end of file diff --git a/libtest.sh b/libtest.sh new file mode 100755 index 0000000..efd20a8 --- /dev/null +++ b/libtest.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +# Compiles example libtest, which produces hello.o and hello.asm, which is then +# compiled with main.cpp and executed for final result +# +# Do note this file is extremely simply for my own personal convenience + +export .env +cargo run --example libtest && \ +clang++ main.cpp hello.o -o main && \ +./main diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..bc5b783 --- /dev/null +++ b/main.cpp @@ -0,0 +1,7 @@ +#include + +extern "C" { +int mainfunc(); +} + +int main() { std::cout << "Return value of test: " << mainfunc() << std::endl; } diff --git a/reid-llvm-lib/Cargo.toml b/reid-llvm-lib/Cargo.toml new file mode 100644 index 0000000..f2bd395 --- /dev/null +++ b/reid-llvm-lib/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "reid-lib" +version = "0.1.0" +edition = "2024" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +## LLVM Bindings +llvm-sys = "160" +## Make it easier to generate errors +thiserror = "1.0.44" \ No newline at end of file diff --git a/reid-llvm-lib/examples/libtest.rs b/reid-llvm-lib/examples/libtest.rs new file mode 100644 index 0000000..b3e2917 --- /dev/null +++ b/reid-llvm-lib/examples/libtest.rs @@ -0,0 +1,25 @@ +use reid_lib::*; + +pub fn main() { + let context = IRContext::new(); + let module = IRModule::new(&context, &"hello".to_owned()); + + let mainfunc = IRFunction::new(&module, &"mainfunc".to_owned()); + + let block = IRBlock::new(&context, &"mainblock".to_owned()); + + let lhs_cmp = IRValue::from_const(&context, 100); + let rhs_cmp = IRValue::from_const(&context, 200); + + let compare = block.less_than(lhs_cmp.into(), rhs_cmp.into()).unwrap(); + + let (lhs, rhs) = block.cond_br(&mainfunc, compare); + + lhs.ret(&mainfunc, IRValue::from_const(&context, 123).into()); + rhs.ret(&mainfunc, IRValue::from_const(&context, 456).into()); + + match module.print_to_string() { + Ok(v) => println!("{}", v), + Err(e) => println!("Err: {:?}", e), + } +} diff --git a/reid-llvm-lib/src/lib.rs b/reid-llvm-lib/src/lib.rs new file mode 100644 index 0000000..476e327 --- /dev/null +++ b/reid-llvm-lib/src/lib.rs @@ -0,0 +1,319 @@ +use std::ffi::{CStr, CString, c_char}; +use std::marker::PhantomData; +use std::mem; +use std::ptr::{null, null_mut}; + +use llvm_sys::analysis::LLVMVerifyModule; +use llvm_sys::target::{ + LLVM_InitializeAllAsmParsers, LLVM_InitializeAllAsmPrinters, LLVM_InitializeAllTargetInfos, + LLVM_InitializeAllTargetMCs, LLVM_InitializeAllTargets, LLVM_InitializeNativeAsmParser, + LLVM_InitializeNativeTarget, LLVMInitializeAMDGPUAsmPrinter, LLVMInitializeX86Target, + LLVMInitializeX86TargetInfo, LLVMInitializeX86TargetMC, LLVMSetModuleDataLayout, +}; +use llvm_sys::target_machine::{ + LLVMCodeGenFileType, LLVMCreateTargetDataLayout, LLVMCreateTargetMachine, + LLVMGetDefaultTargetTriple, LLVMGetFirstTarget, LLVMGetHostCPUFeatures, LLVMGetHostCPUName, + LLVMGetTargetFromTriple, LLVMTargetMachineEmitToFile, LLVMTargetMachineEmitToMemoryBuffer, +}; +use llvm_sys::transforms::pass_manager_builder::{ + LLVMPassManagerBuilderCreate, LLVMPassManagerBuilderPopulateModulePassManager, + LLVMPassManagerBuilderSetOptLevel, +}; +use llvm_sys::{ + LLVMBasicBlock, LLVMBuilder, LLVMContext, LLVMModule, LLVMType, LLVMValue, core::*, prelude::*, +}; + +fn into_cstring>(value: T) -> CString { + let string = value.into(); + unsafe { CString::from_vec_with_nul_unchecked((string + "\0").into_bytes()) } +} + +fn from_cstring(value: *mut c_char) -> Option { + if value.is_null() { + None + } else { + unsafe { CString::from_raw(value).into_string().ok() } + } +} + +fn cstring_to_err(value: *mut c_char) -> Result<(), String> { + from_cstring(value) + .filter(|s| !s.is_empty()) + .map_or(Ok(()), |s| Err(s)) +} + +pub trait IRType { + const SIGNED: LLVMBool; + unsafe fn llvm_type(context: &IRContext) -> LLVMTypeRef; +} + +impl IRType for bool { + const SIGNED: LLVMBool = 0; + unsafe fn llvm_type(context: &IRContext) -> LLVMTypeRef { + unsafe { LLVMInt1TypeInContext(context.context) } + } +} + +impl IRType for i32 { + const SIGNED: LLVMBool = 1; + unsafe fn llvm_type(context: &IRContext) -> LLVMTypeRef { + unsafe { LLVMInt32TypeInContext(context.context) } + } +} + +pub struct IRValue(PhantomData, IROpaqueValue); + +impl IRValue { + unsafe fn from_runtime(t: LLVMTypeRef, value: LLVMValueRef) -> IRValue { + IRValue(PhantomData, IROpaqueValue(t, value)) + } +} + +impl> IRValue { + pub fn from_const(context: &IRContext, value: T) -> Self { + unsafe { + let t = T::llvm_type(context); + let value = LLVMConstInt(t, value.into() as u64, T::SIGNED); + IRValue(PhantomData, IROpaqueValue(t, value)) + } + } +} + +impl From> for IROpaqueValue { + fn from(value: IRValue) -> Self { + value.1 + } +} + +pub struct IROpaqueValue(LLVMTypeRef, LLVMValueRef); + +pub struct IRContext { + context: *mut LLVMContext, + builder: *mut LLVMBuilder, +} + +impl IRContext { + pub fn new() -> IRContext { + unsafe { + // Set up a context, module and builder in that context. + let context = LLVMContextCreate(); + let builder = LLVMCreateBuilderInContext(context); + + IRContext { context, builder } + } + } +} + +impl Drop for IRContext { + fn drop(&mut self) { + // Clean up. Values created in the context mostly get cleaned up there. + unsafe { + LLVMDisposeBuilder(self.builder); + LLVMContextDispose(self.context); + } + } +} + +pub struct IRModule<'a> { + context: &'a IRContext, + module: *mut LLVMModule, +} + +impl<'a> IRModule<'a> { + pub fn new(context: &'a IRContext, name: &String) -> IRModule<'a> { + unsafe { + let module = + LLVMModuleCreateWithNameInContext(into_cstring(name).as_ptr(), context.context); + + IRModule { context, module } + } + } + + pub fn print_to_string(&self) -> Result<&str, String> { + unsafe { + // let pmb = LLVMPassManagerBuilderCreate(); + // LLVMPassManagerBuilderSetOptLevel(pmb, 0); + // let pm = LLVMCreatePassManager(); + // LLVMPassManagerBuilderPopulateModulePassManager(pmb, pm); + // println!("{}", LLVMRunPassManager(pm, self.module)); + + LLVM_InitializeAllTargets(); + LLVM_InitializeAllTargetInfos(); + LLVM_InitializeAllTargetMCs(); + LLVM_InitializeAllAsmParsers(); + LLVM_InitializeAllAsmPrinters(); + + let triple = LLVMGetDefaultTargetTriple(); + + let mut target: _ = null_mut(); + let mut err: _ = null_mut(); + LLVMGetTargetFromTriple(c"x86_64-unknown-linux-gnu".as_ptr(), &mut target, &mut err); + println!("{:?}, {:?}", from_cstring(triple), target); + cstring_to_err(err).unwrap(); + + let target_machine = LLVMCreateTargetMachine( + target, + triple, + c"generic".as_ptr(), + c"".as_ptr(), + llvm_sys::target_machine::LLVMCodeGenOptLevel::LLVMCodeGenLevelNone, + llvm_sys::target_machine::LLVMRelocMode::LLVMRelocDefault, + llvm_sys::target_machine::LLVMCodeModel::LLVMCodeModelDefault, + ); + + let data_layout = LLVMCreateTargetDataLayout(target_machine); + LLVMSetModuleDataLayout(self.module, data_layout); + + let mut err = null_mut(); + LLVMVerifyModule( + self.module, + llvm_sys::analysis::LLVMVerifierFailureAction::LLVMPrintMessageAction, + &mut err, + ); + cstring_to_err(err).unwrap(); + + let mut err = null_mut(); + LLVMTargetMachineEmitToFile( + target_machine, + self.module, + CString::new("hello.asm").unwrap().into_raw(), + LLVMCodeGenFileType::LLVMAssemblyFile, + &mut err, + ); + cstring_to_err(err).unwrap(); + + let mut err = null_mut(); + LLVMTargetMachineEmitToFile( + target_machine, + self.module, + CString::new("hello.o").unwrap().into_raw(), + LLVMCodeGenFileType::LLVMObjectFile, + &mut err, + ); + cstring_to_err(err).unwrap(); + + Ok(CStr::from_ptr(LLVMPrintModuleToString(self.module)) + .to_str() + .expect("UTF8-err")) + } + } +} + +impl<'a> Drop for IRModule<'a> { + fn drop(&mut self) { + // Clean up. Values created in the context mostly get cleaned up there. + unsafe { + LLVMDisposeModule(self.module); + } + } +} + +pub struct IRFunction<'a> { + pub module: &'a IRModule<'a>, + pub functionref: *mut LLVMValue, +} + +impl<'a> IRFunction<'a> { + pub fn new(module: &'a IRModule<'a>, name: &String) -> IRFunction<'a> { + unsafe { + // TODO, fix later! + let return_type = LLVMInt32TypeInContext(module.context.context); + let mut argts = []; + let func_type = + LLVMFunctionType(return_type, argts.as_mut_ptr(), argts.len() as u32, 0); + + let functionref = + LLVMAddFunction(module.module, into_cstring(name).as_ptr(), func_type); + + IRFunction { + module, + functionref, + } + } + } +} + +pub struct IRBlock<'a> { + context: &'a IRContext, + blockref: *mut LLVMBasicBlock, + inserted: bool, +} + +impl<'a> IRBlock<'a> { + pub fn new(context: &'a IRContext, name: &String) -> IRBlock<'a> { + unsafe { + let blockref = + LLVMCreateBasicBlockInContext(context.context, into_cstring(name).as_ptr()); + + IRBlock { + context, + blockref, + inserted: false, + } + } + } + + pub fn less_than(&self, lhs: IROpaqueValue, rhs: IROpaqueValue) -> Result, ()> { + let IROpaqueValue(t1, lhs) = lhs; + let IROpaqueValue(t2, rhs) = rhs; + + if t1 != t2 { + Err(()) + } else { + unsafe { + let builder = self.context.builder; + LLVMPositionBuilderAtEnd(builder, self.blockref); + let value = LLVMBuildICmp( + builder, + llvm_sys::LLVMIntPredicate::LLVMIntSLT, + lhs, + rhs, + c"asd".as_ptr(), + ); + Ok(IRValue::from_runtime(bool::llvm_type(&self.context), value)) + } + } + } + + pub fn cond_br( + self, + function: &IRFunction, + value: IRValue, + ) -> (IRBlock<'a>, IRBlock<'a>) { + let lhs = IRBlock::new(self.context, &"lhs".to_owned()); + let rhs = IRBlock::new(self.context, &"rhs".to_owned()); + unsafe { + let builder = self.context.builder; + LLVMPositionBuilderAtEnd(builder, self.blockref); + LLVMBuildCondBr(builder, value.1.1, lhs.blockref, rhs.blockref); + self.append(function); + (lhs, rhs) + } + } + + pub fn ret(self, function: &IRFunction, value: IROpaqueValue) { + unsafe { + let builder = self.context.builder; + LLVMPositionBuilderAtEnd(builder, self.blockref); + LLVMBuildRet(builder, value.1); + self.append(function); + } + } + + unsafe fn append(mut self, function: &IRFunction<'a>) { + unsafe { + LLVMAppendExistingBasicBlock(function.functionref, self.blockref); + self.inserted = true; + } + } +} + +impl<'a> Drop for IRBlock<'a> { + fn drop(&mut self) { + unsafe { + if !self.inserted { + LLVMDeleteBasicBlock(self.blockref); + } + } + } +} diff --git a/reid/src/codegen/llvm.rs b/reid/src/codegen/llvm.rs index de9528b..51f0ea7 100644 --- a/reid/src/codegen/llvm.rs +++ b/reid/src/codegen/llvm.rs @@ -4,8 +4,12 @@ use std::mem; use std::ptr::null_mut; use llvm_sys::analysis::LLVMVerifyModule; +use llvm_sys::transforms::pass_manager_builder::{ + self, LLVMOpaquePassManagerBuilder, LLVMPassManagerBuilderCreate, + LLVMPassManagerBuilderSetOptLevel, +}; use llvm_sys::{ - core::*, prelude::*, LLVMBasicBlock, LLVMBuilder, LLVMContext, LLVMModule, LLVMType, LLVMValue, + LLVMBasicBlock, LLVMBuilder, LLVMContext, LLVMModule, LLVMType, LLVMValue, core::*, prelude::*, }; use crate::ast;