diff --git a/README.md b/README.md index 9857fce..8dd98a4 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,11 @@ Author/Maintainer: Giordano Salvador <73959795+e3m3@users.noreply.github.com> [![MacOS 14](https://github.com/e3m3/calcc-rust/actions/workflows/macos-14.yaml/badge.svg?event=workflow_dispatch)](https://github.com/e3m3/calcc-rust/actions/workflows/macos-14.yaml) Learning [Rust][1] [[1]] by implementing the calc langauge using the [llvm-sys][2] [[2]] crate. -Implements the calc language, inspired by the [C++][3] [[3]] implementation presented by Macke and Kwan in [[4]] and [[5]]. +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/`). +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, LLVM bitcode, an object file, or executable file [[6]]. @@ -81,10 +83,11 @@ term ::= factor ( Slash | Star ) factor Notes: -* The grammar rules above use the `tokenkind` as a shorthand for a `token` object as described by the lexer rules. +* The grammar rules above use the `tokenkind` as a shorthand for a `token` object as described + by the lexer rules. -* In the AST, a factor with a leading `Minus` token is represented as a subtraction expression where the left term - is `Number` with the constant value `0`. +* In the AST, a factor with a leading `Minus` token is represented as a subtraction expression + where the left term is `Number` with the constant value `0`. ## Prequisites @@ -99,8 +102,8 @@ Notes: * 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`. + * By default, `tests/lit-tests-llvm.rs` will search for the lit executable in + `$PYTHON_VENV_PATH/bin` (if it exists) or the system's `/usr/bin`. * [docker|podman] (for testing/containerization) diff --git a/src/lex.rs b/src/lex.rs index 68ce8ff..e559bc5 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -215,7 +215,12 @@ impl <'a, T: Read> Lexer<'a, T> { } else if Self::is_letter(c) { let pos_end: usize = self.collect_token_sequence(pos_start + 1, Self::is_ident); let text = String::from(&self.line[pos_start..pos_end]); - self.form_token(t, pos_start, pos_end, if text == "with" {TokenKind::With} else {TokenKind::Ident}); + self.form_token( + t, + pos_start, + pos_end, + if text == "with" {TokenKind::With} else {TokenKind::Ident} + ); } else if Self::is_slash(c) { if self.has_next_in_line(pos_start + 1) { c = self.next_char_in_line(pos_start + 1); diff --git a/src/main.rs b/src/main.rs index a74d3c3..697820b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -106,7 +106,11 @@ fn set_codegen_type(options: &mut RunOptions, codegen_type: CodeGenType) -> () { if options.codegen_type == CodeGenType::Unset { options.codegen_type = codegen_type; } else { - eprintln!("Incompatible compiler flags for output type: {} and {}", options.codegen_type, codegen_type); + eprintln!( + "Incompatible compiler flags for output type: {} and {}", + options.codegen_type, + codegen_type + ); exit(ExitCode::ArgParseError); } } @@ -145,12 +149,27 @@ fn get_extension_from_filename(name: &str) -> ExtType { /// Checks to ensure valid combination for BodyType, CodeGenType, and OutputType fn check_options_configuration(options: &RunOptions, output: &OutputType) -> () { match *output { - OutputType::Stdout => (), + OutputType::Stdout => if !options.early_exit() { + match options.codegen_type { + CodeGenType::Object => { + eprintln!("Output to Stdout not supported for object files"); + exit(ExitCode::ArgParseError); + }, + CodeGenType::Executable => { + eprintln!("Output to Stdout not supported for executable files"); + exit(ExitCode::ArgParseError); + }, + _ => (), + } + }, OutputType::File(f) => { let t = options.codegen_type; match get_extension_from_filename(f) { ExtType::None => if t != CodeGenType::Executable { - eprintln!("Output name (no/unknown extension) should match codegen type: {} specified", t); + eprintln!( + "Output name (no/unknown extension) should match codegen type: {} specified", + t + ); exit(ExitCode::ArgParseError); }, ExtType::BC => if t != CodeGenType::Bitcode { @@ -219,7 +238,7 @@ fn parse_args<'a>( "--llvmir" => set_codegen_type(options, CodeGenType::Llvmir), "--no-main" => set_body_type(options, BodyType::NoMain), "--notarget" => options.no_target = true, - "-o" => *output = OutputType::File(parse_arg_after(args, &mut i)), + "-o" => *output = OutputType::new(parse_arg_after(args, &mut i)), "-O0" => options.opt_level = OptLevel::O0, "-O1" => options.opt_level = OptLevel::O1, "-O2" => options.opt_level = OptLevel::O2, @@ -291,7 +310,7 @@ fn parse_arg_complex<'a>( match &arg[0..j] { "-e" => *input = InputType::Expr(&arg[j + 1..]), "--expr" => *input = InputType::Expr(&arg[j + 1..]), - "-o" => *output = OutputType::File(&arg[j + 1..]), + "-o" => *output = OutputType::new(&arg[j + 1..]), _ => { eprintln!("Unrecognized argument '{}'", arg); help(ExitCode::ArgParseError); diff --git a/src/maingen.rs b/src/maingen.rs index 9aa094a..d322c64 100644 --- a/src/maingen.rs +++ b/src/maingen.rs @@ -89,7 +89,12 @@ impl <'a, 'b> MainGen<'a, 'b> { let f_name = ModuleBundle::value_name(self.bundle.name.as_str()); unsafe { let t_ret = self.bundle.t_i32; - let t_f = LLVMFunctionType(t_ret, param_types.as_mut_ptr(), param_types.len() as u32, false as LLVMBool); + let t_f = LLVMFunctionType( + t_ret, + param_types.as_mut_ptr(), + param_types.len() as u32, + false as LLVMBool + ); let f = LLVMAddFunction(self.bundle.module, f_name.as_ptr() as *const c_char, t_f); self.bundle.f = Some(f); self.bundle.f_sig = Some(FunctionSignature::new(t_ret, param_types)); @@ -205,7 +210,12 @@ impl <'a, 'b> MainGen<'a, 'b> { let mut param_types: Vec = vec![self.bundle.t_opaque, callee_sig.t_ret]; let n = param_types.len(); unsafe { - let t_f = LLVMFunctionType(self.bundle.t_i32, param_types.as_mut_ptr(), n as u32, true as LLVMBool); + let t_f = LLVMFunctionType( + self.bundle.t_i32, + param_types.as_mut_ptr(), + n as u32, + true as LLVMBool + ); let _ = LLVMBuildCall2( self.bundle.builder, t_f, @@ -219,7 +229,11 @@ impl <'a, 'b> MainGen<'a, 'b> { } } - fn gen_entry_stack(&mut self, bb_entry: LLVMBasicBlockRef, f_sig: &'a FunctionSignature) -> Vec { + fn gen_entry_stack( + &mut self, + bb_entry: LLVMBasicBlockRef, + f_sig: &'a FunctionSignature + ) -> Vec { unsafe { LLVMPositionBuilderAtEnd(self.bundle.builder, bb_entry); } let _value_ret = self.bundle.gen_alloca(NAME_RETVAL, self.bundle.t_i32); let _value_argc = self.bundle.gen_alloca(NAME_ARGC, self.bundle.t_i32); @@ -239,7 +253,11 @@ impl <'a, 'b> MainGen<'a, 'b> { let f = self.bundle.f.unwrap(); let name_block = ModuleBundle::value_name("ret_label"); unsafe { - let bb = LLVMAppendBasicBlockInContext(self.bundle.context, f, name_block.as_ptr() as *const c_char); + let bb = LLVMAppendBasicBlockInContext( + self.bundle.context, + f, + name_block.as_ptr() as *const c_char + ); LLVMPositionBuilderAtEnd(self.bundle.builder, bb); bb } @@ -261,7 +279,12 @@ impl <'a, 'b> MainGen<'a, 'b> { } } - fn gen_err_block(&mut self, bb_err: LLVMBasicBlockRef, bb_ret: LLVMBasicBlockRef, callee_arg_len: usize) -> () { + fn gen_err_block( + &mut self, + bb_err: LLVMBasicBlockRef, + bb_ret: LLVMBasicBlockRef, + callee_arg_len: usize + ) -> () { unsafe { LLVMPositionBuilderAtEnd(self.bundle.builder, bb_err) }; let name_retval = ModuleBundle::value_name(NAME_RETVAL); let name_stderr = ModuleBundle::value_name(NAME_STDERR); @@ -384,7 +407,9 @@ impl <'a, 'b> MainGen<'a, 'b> { let mut args: Vec = Vec::new(); for i in 1..callee_values.len() { let name = self.bundle.scope.next_value_name(); - let t_arg = callee_sig.params.get(i - 1).expect("The first item in callee_values should be the return value"); + let t_arg = callee_sig.params + .get(i - 1) + .expect("The first item in callee_values should be the return value"); let value_arg = callee_values.get(i).unwrap(); let value_load = unsafe { LLVMBuildLoad2( @@ -427,7 +452,10 @@ impl <'a, 'b> MainGen<'a, 'b> { fn declare_global_strings(&mut self) -> () { let _value_argerr: LLVMValueRef = self.bundle.declare_global_string(NAME_ARG_ERR, STRING_ARG_ERR); - let _value_result_str: LLVMValueRef = self.bundle.declare_global_string(NAME_RESULT_STR, STRING_RESULT_STR); + let _value_result_str: LLVMValueRef = self.bundle.declare_global_string( + NAME_RESULT_STR, + STRING_RESULT_STR + ); let _value_usage: LLVMValueRef = self.bundle.declare_global_string(NAME_USAGE, STRING_USAGE); } diff --git a/src/module.rs b/src/module.rs index 6e0a2d7..73c1aed 100644 --- a/src/module.rs +++ b/src/module.rs @@ -5,6 +5,7 @@ extern crate llvm_sys as llvm; use llvm::analysis::LLVMVerifierFailureAction; use llvm::analysis::LLVMVerifyModule; +use llvm::bit_writer::LLVMWriteBitcodeToFD; use llvm::bit_writer::LLVMWriteBitcodeToFile; use llvm::core::LLVMAddFunction; use llvm::core::LLVMBuildAlloca; @@ -129,7 +130,12 @@ impl <'a> ModuleBundle<'a> { is_va_arg: bool, ) -> LLVMValueRef { let value = unsafe { - let t_f = LLVMFunctionType(t_ret, params.as_mut_ptr(), params.len() as c_uint, is_va_arg as LLVMBool); + let t_f = LLVMFunctionType( + t_ret, + params.as_mut_ptr(), + params.len() as c_uint, + is_va_arg as LLVMBool + ); LLVMAddFunction(self.module, name.as_ptr() as *const c_char, t_f) }; self.insert_value(name, value); @@ -198,8 +204,18 @@ impl <'a> ModuleBundle<'a> { } pub fn write_bitcode_to_file(&mut self, f: &str) -> () { - let name = format!("{}\0", f); - let result: c_int = unsafe { LLVMWriteBitcodeToFile(self.module, name.as_ptr() as *const c_char) }; + let (name, result) = if f == "-" { + let result: c_int = unsafe { + LLVMWriteBitcodeToFD(self.module, 1, false as LLVMBool, false as LLVMBool) + }; + ("Stdout".to_string(), result) + } else { + let name = format!("{}\0", f); + let result: c_int = unsafe { + LLVMWriteBitcodeToFile(self.module, name.as_ptr() as *const c_char) + }; + (name, result) + }; if result != 0 as c_int { eprintln!("Failed to write module to file '{}'", name); exit(ExitCode::WriteError); @@ -257,9 +273,10 @@ impl <'a> ModuleBundle<'a> { match *output { OutputType::Stdout => { match options.codegen_type { - CodeGenType::Llvmir => println!("{}", self), - _ => { - eprintln!("Unimplemented: writing {} to Stdout", options.codegen_type); + CodeGenType::Llvmir => println!("{}", self), + CodeGenType::Bitcode => self.write_bitcode_to_file("-"), + _ => { + eprintln!("Unexpected '{}' for output to stdout", options.codegen_type); return false; }, } diff --git a/src/options.rs b/src/options.rs index 6f12bf1..23033b5 100644 --- a/src/options.rs +++ b/src/options.rs @@ -71,6 +71,15 @@ pub enum OutputType<'a> { File(&'a str), } +impl <'a> OutputType<'a> { + pub fn new(f: &'a str) -> Self { + match f { + "-" => OutputType::Stdout, + _ => OutputType::File(f), + } + } +} + impl <'a> fmt::Display for OutputType<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let s = match self { diff --git a/src/parse.rs b/src/parse.rs index 11589bc..b6b72eb 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -63,7 +63,11 @@ impl <'a> Parser<'a> { let t: &Token = self.get_token(iter); if t.is(k) { if self.options.verbose { - eprintln!("Consumed expected token '{}' at position '{}'", token_kind_to_string(k), iter.position); + eprintln!( + "Consumed expected token '{}' at position '{}'", + token_kind_to_string(k), + iter.position + ); } iter.token = t.clone(); iter.position += 1; diff --git a/tests/lit-tests/lit.cfg b/tests/lit-tests/lit.cfg index 59c963a..7502df6 100644 --- a/tests/lit-tests/lit.cfg +++ b/tests/lit-tests/lit.cfg @@ -11,6 +11,7 @@ config.substitutions.append(("@diff", "diff")) config.substitutions.append(("@filecheck", "FileCheck --color --vv")) config.substitutions.append(("@head", "head")) config.substitutions.append(("@llc", "llc")) +config.substitutions.append(("@lld", "lld")) config.substitutions.append(("@llvm-as", "llvm-as")) config.substitutions.append(("@llvm-dis", "llvm-dis")) config.substitutions.append(("@llvm-opt", "opt")) diff --git a/tests/lit-tests/option_c_main2.calc b/tests/lit-tests/option_c_main2.calc index 99c6dbf..937f145 100644 --- a/tests/lit-tests/option_c_main2.calc +++ b/tests/lit-tests/option_c_main2.calc @@ -1,4 +1,4 @@ -// RUN: not @calcc %s 2>&1 | @filecheck %s +// RUN: not @calcc %s -o %t.out 2>&1 | @filecheck %s // REQUIRES: OS_MACOS diff --git a/tests/lit-tests/output_stdout1.calc b/tests/lit-tests/output_stdout1.calc new file mode 100644 index 0000000..d961218 --- /dev/null +++ b/tests/lit-tests/output_stdout1.calc @@ -0,0 +1,16 @@ +// RUN: @calcc -O0 --llvmir -o - %s | @filecheck %s --check-prefix=CHECK_A +// RUN: @calcc -O0 --bitcode -o - %s | @llvm-dis -o - | @filecheck %s --check-prefix=CHECK_A +// RUN: not @calcc -c -o - %s 2>&1 | @filecheck %s --check-prefix=CHECK_B +// RUN: not @calcc -o - %s 2>&1 | @filecheck %s --check-prefix=CHECK_C + +// UNSUPPORTED: OS_MACOS + +// CHECK_A-LABEL: define i64 @calcc_main() { +// CHECK_A-LABEL: entry: +// CHECK_A: ret i64 10 +// CHECK_A: } + +// CHECK_B: Output to Stdout not supported for object files +// CHECK_C: Output to Stdout not supported for executable files + +10 diff --git a/tests/lit-tests/output_stdout2.calc b/tests/lit-tests/output_stdout2.calc new file mode 100644 index 0000000..a5e5848 --- /dev/null +++ b/tests/lit-tests/output_stdout2.calc @@ -0,0 +1,14 @@ +// RUN: @calcc -O0 -C --llvmir -o - %s | @filecheck %s --check-prefix=CHECK_A +// RUN: @calcc -O0 -C --bitcode -o - %s | @llvm-dis -o - | @filecheck %s --check-prefix=CHECK_A +// RUN: not @calcc -C -c -o - %s 2>&1 | @filecheck %s --check-prefix=CHECK_B +// RUN: not @calcc -C -o - %s 2>&1 | @filecheck %s --check-prefix=CHECK_C + +// CHECK_A-LABEL: define i64 @calcc_main() { +// CHECK_A-LABEL: entry: +// CHECK_A: ret i64 10 +// CHECK_A: } + +// CHECK_B: Output to Stdout not supported for object files +// CHECK_C: Output to Stdout not supported for executable files + +10