Skip to content

Commit

Permalink
Enable object file and executable output
Browse files Browse the repository at this point in the history
*	Added C-derived main code generation, using clang

*	Use llc and clang for object files and executables

*	Added cli shorthand flags for convenience

*	Removed lit crate dependency and tests

*	Refactored code and tests

*	Version bump
  • Loading branch information
e3m3 committed Sep 20, 2024
1 parent b3e79be commit 984f0c0
Show file tree
Hide file tree
Showing 65 changed files with 1,394 additions and 488 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ Cargo.lock

*.bak

tests/lit-llvm/.lit_test_times.txt
tests/lit-llvm/Output
tests/lit-tests/.lit_test_times.txt
tests/lit-tests/Output
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "calcc"
version = "0.1.0"
version = "0.2.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand All @@ -9,7 +9,6 @@ edition = "2021"
llvm-sys = { version = "=181.1.1", features = ["force-dynamic"] }

[dev-dependencies]
lit = "1.0.4"

[lints.clippy]
unused_unit = "allow"
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Learning [Rust][1] [[1]] by implementing the calc langauge using the [llvm-sys][
Implements the calc language, inspired by the [C++][3] [[3]] implementation presented by Macke and Kwan in [[4]] and [[5]].

Accepted factors in the grammar have been extended for convenience (see `src/{lex,parse}.rs` and `tests/lit-llvm/`).
The output of the compiler is LLVM IR or LLVM bytecode [[6]].
The output of the compiler is LLVM IR, LLVM bitcode, an object file, or executable file [[6]].


## Language
Expand Down Expand Up @@ -95,7 +95,9 @@ Notes:

* llvm18 and llvm-sys (or llvm version matching llvm-sys)

* python3-lit, FileCheck, clang (for testing)
* clang-18 (for executables and '-C|--c-main' flags)

* python3-lit, FileCheck (for testing)

* By default, `tests/lit-tests-llvm.rs` will search for the lit executable in the `$PYTHON_VENV_PATH/bin`
(if it exists) or the system's `/usr/bin`.
Expand Down Expand Up @@ -143,17 +145,26 @@ usage: calcc [OPTIONS] <INPUT>
INPUT '-' (i.e., Stdin) or a file path
OPTIONS:
--ast Print the AST after parsing
-b|--bitcode Output LLVM bitcode (post-optimization) (.bc if used with -o)
-c Output an object file (post-optimization) (.o if used with -o)
--drop Drop unknown tokens instead of failing
-e|--expr[=]<E> Process expression E instead of INPUT file
-h|--help Print this list of command line options
--lex Exit after running the lexer
-S|--llvmir Exit after outputting LLVM IR (post-optimization) instead of byte code
--nomain Omit linking with main module (i.e., output kernel only)
--notarget Omit target specific configuration in LLVM IR/bytecode
-o[=]<F> Output to LLVM IR (.ll) or bytecode (.bc) file F instead of Stdout
--ir Exit after printing IR (pre-optimization)
-S|--llvmir Output LLVM IR (post-optimization) (.ll if used with -o)
-k|--no-main Omit linking with main module (i.e., output kernel only)
When this option is selected, an executable cannot be generated
--notarget Omit target specific configuration in LLVM IR/bitcode
-o[=]<F> Output to file F instead of Stdout
If no known extension is used (.bc|.exe|.ll|.o) an executable is assumed
An executable requires llc and clang to be installed
-O<0|1|2|3> Set the optimization level (default: O2)
--parse Exit after running the parser
--sem Exit after running the semantics check
-C|--c-main Link with a C-derived main module (src/main.c.template)
This option is required for generating object files and executables on MacOS
and requires clang to be installed
-v|--verbose Enable verbose output
--version Display the package version and license information
```
Expand Down
91 changes: 91 additions & 0 deletions src/command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2024, Giordano Salvador
// SPDX-License-Identifier: BSD-3-Clause

use crate::exit_code;
use exit_code::exit;
use exit_code::ExitCode;

use std::io::Write;
use std::process;
use std::process::Stdio;

pub struct CommandResult {
pub success: bool,
pub stdout: Option<String>,
}

impl CommandResult {
pub fn new(success: bool, stdout: Option<String>) -> Self {
CommandResult{success, stdout}
}
}

pub struct Command {}

impl Command {
pub fn run(bin: &str, args: &[&str]) -> CommandResult {
let output = match process::Command::new(bin).args(args.iter()).output() {
Ok(output) => output,
Err(msg) => {
eprintln!("Failed to run with {}: {}", bin, msg);
exit(ExitCode::CommandError);
},
};
let stderr: &[u8] = output.stderr.as_slice();
let stdout: &[u8] = output.stdout.as_slice();
if !stderr.is_empty() {
eprintln!("{} stderr:\n{}", bin, std::str::from_utf8(stderr).unwrap());
}
let stdout_opt = if !stdout.is_empty() {
Some(std::str::from_utf8(stdout).unwrap().to_string())
} else {
None
};
CommandResult::new(output.status.success(), stdout_opt)
}

pub fn run_with_input(bin: &str, args: &[&str], input: &str) -> CommandResult {
let mut proc = match process::Command::new(bin)
.args(args.iter())
.stdin(Stdio::piped())
.spawn() {
Ok(proc) => proc,
Err(msg) => {
eprintln!("Failed to spawn {} child process: {}", bin, msg);
exit(ExitCode::CommandError);
},
};
let proc_stdin = match proc.stdin.as_mut() {
Some(stdin) => stdin,
None => {
eprintln!("Failed to get {} process stdin handle", bin);
exit(ExitCode::CommandError);
},
};
match proc_stdin.write_all(input.as_bytes()) {
Ok(()) => (),
Err(msg) => {
eprintln!("Failed to write input to {} process stdin: {}", bin, msg);
exit(ExitCode::CommandError);
},
};
let output = match proc.wait_with_output() {
Ok(output) => output,
Err(msg) => {
eprintln!("Failed to run with {}: {}", bin, msg);
exit(ExitCode::CommandError);
},
};
let stderr: &[u8] = output.stderr.as_slice();
let stdout: &[u8] = output.stdout.as_slice();
if !stderr.is_empty() {
eprintln!("{} stderr:\n{}", bin, std::str::from_utf8(stderr).unwrap());
}
let stdout_opt = if !stdout.is_empty() {
Some(std::str::from_utf8(stdout).unwrap().to_string())
} else {
None
};
CommandResult::new(output.status.success(), stdout_opt)
}
}
12 changes: 7 additions & 5 deletions src/exit_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ pub enum ExitCode {
SemanticError = 4,
ModuleError = 5,
IRGenError = 6,
_MainGenError = 7,
VerifyError = 8,
TargetError = 9,
LinkError = 10,
WriteError = 11,
MainGenError = 7,
MainGenCError = 8,
VerifyError = 9,
TargetError = 10,
LinkError = 11,
WriteError = 12,
CommandError = 13,
}

pub fn exit(code: ExitCode) -> ! {
Expand Down
15 changes: 13 additions & 2 deletions src/irgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,18 @@

extern crate llvm_sys as llvm;

use llvm::core::*;
use llvm::core::LLVMAddFunction;
use llvm::core::LLVMAppendBasicBlockInContext;
use llvm::core::LLVMBuildLoad2;
use llvm::core::LLVMBuildNSWAdd;
use llvm::core::LLVMBuildNSWMul;
use llvm::core::LLVMBuildNSWSub;
use llvm::core::LLVMBuildRet;
use llvm::core::LLVMBuildSDiv;
use llvm::core::LLVMBuildStore;
use llvm::core::LLVMFunctionType;
use llvm::core::LLVMGetParam;
use llvm::core::LLVMPositionBuilderAtEnd;
use llvm::prelude::LLVMBasicBlockRef;
use llvm::prelude::LLVMBool;
use llvm::prelude::LLVMTypeRef;
Expand Down Expand Up @@ -34,7 +45,7 @@ pub struct IRGen<'a, 'b> {
}

impl <'a, 'b> IRGen<'a, 'b> {
pub fn new(bundle: &'a mut ModuleBundle<'b>) -> Self {
fn new(bundle: &'a mut ModuleBundle<'b>) -> Self {
IRGen{bundle}
}

Expand Down
16 changes: 3 additions & 13 deletions src/lex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub enum TokenKind {
}

pub fn token_kind_to_string(k: TokenKind) -> String {
String::from(match k {
match k {
TokenKind::Comma => "Comma",
TokenKind::Comment => "Comment",
TokenKind::Colon => "Colon",
Expand All @@ -51,7 +51,7 @@ pub fn token_kind_to_string(k: TokenKind) -> String {
TokenKind::Star => "Star",
TokenKind::Unknown => "Unknown",
TokenKind::With => "With",
})
}.to_string()
}

#[derive(Clone)]
Expand Down Expand Up @@ -273,17 +273,7 @@ impl <'a, T: Read> Lexer<'a, T> {
}

fn is_other(c: char) -> bool {
match c {
',' => true,
':' => true,
'-' => true,
'(' => true,
')' => true,
'+' => true,
'/' => true,
'*' => true,
_ => false,
}
matches!(c, ',' | ':' | '-' | '(' | ')' | '+' | '/' | '*')
}

fn is_whitespace(c: char) -> bool {
Expand Down
41 changes: 41 additions & 0 deletions src/main.c.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2024, Giordano Salvador
// SPDX-License-Identifier: BSD-3-Clause

/// Description: A C-stub used to generate the main module for linking during compile time.
/// Values prefixed by `@@` are expected inputs from the compiler for text substitution.
/// Inputs:
/// * NUM_ARGS : a `usize` for the length of the parameters list
/// * USAGE_ARGS : a comma separated list of args for the usage prompt
/// : (e.g., <arg0>, <arg1>, ...
/// * PARAM_TYPES_LIST : a comma separated list of types for the callee prototype
/// * PARAM_DECLS_LIST : a sequence of statements assigning temporaries for the callee
/// (e.g., const t_i64 p0 = (t_i64)atoll(argv[BASE + 0]); ... )
/// * PARAMS_LIST : a comma separated list of uses of the temporaries for the callee
/// (e.g., p0, p1, ... )

#include <stdio.h>
#include <stdlib.h>

#define BASE 1
#define NUM_ARGS @@NUM_ARGS
#define USAGE "<exe> @@USAGE_ARGS\n"

typedef long long t_i64;
extern t_i64 calcc_main(@@PARAM_TYPES_LIST);

int main(int argc, char **argv) {
if (argc != BASE + NUM_ARGS) {
(void) fprintf(stderr, "Invalid number of args to main. Expected %d args\n", NUM_ARGS);
(void) fprintf(stderr, USAGE);
return 1;
}

/* Parameter declaration section: */
@@PARAM_DECLS_LIST

/* Function call section: */
const t_i64 result = calcc_main(@@PARAMS_LIST);
(void) printf("calcc_main result: %lld\n", result);

return 0;
}
Loading

0 comments on commit 984f0c0

Please sign in to comment.