Add rudamentary LLVM lib stuff, make a fully compiling executable

This commit is contained in:
Sofia 2025-06-24 23:10:44 +03:00
parent 6d3d0fd03e
commit 5f93b7c9c2
9 changed files with 393 additions and 4 deletions

4
.gitignore vendored
View File

@ -2,4 +2,6 @@
src/old_llvm
/target
/.vscode
.env
.env
hello.*
main

10
Cargo.lock generated
View File

@ -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"

View File

@ -1,4 +1,5 @@
[workspace]
members = [
"reid"
"reid",
"reid-llvm-lib"
]

11
libtest.sh Executable file
View File

@ -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

7
main.cpp Normal file
View File

@ -0,0 +1,7 @@
#include <iostream>
extern "C" {
int mainfunc();
}
int main() { std::cout << "Return value of test: " << mainfunc() << std::endl; }

12
reid-llvm-lib/Cargo.toml Normal file
View File

@ -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"

View File

@ -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),
}
}

319
reid-llvm-lib/src/lib.rs Normal file
View File

@ -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<T: Into<String>>(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<String> {
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<T: IRType>(PhantomData<T>, IROpaqueValue);
impl<T: IRType> IRValue<T> {
unsafe fn from_runtime(t: LLVMTypeRef, value: LLVMValueRef) -> IRValue<T> {
IRValue(PhantomData, IROpaqueValue(t, value))
}
}
impl<T: IRType + Into<i64>> IRValue<T> {
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<T: IRType> From<IRValue<T>> for IROpaqueValue {
fn from(value: IRValue<T>) -> 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<IRValue<bool>, ()> {
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<bool>,
) -> (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);
}
}
}
}

View File

@ -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;