From c550a22a72cc61472c12a657af594cbdc65532c2 Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Fri, 14 Aug 2020 23:26:32 -0400 Subject: [PATCH 1/7] Split saltwater into crates --- Cargo.toml | 43 ++- saltwater-codegen/Cargo.toml | 23 ++ {src/ir => saltwater-codegen}/expr.rs | 8 +- src/ir/mod.rs => saltwater-codegen/lib.rs | 258 +++++++----------- {src/ir => saltwater-codegen}/static_init.rs | 176 ++++++------- {src/ir => saltwater-codegen}/stmt.rs | 14 +- saltwater-parser/Cargo.toml | 34 +++ {src => saltwater-parser}/analyze/expr.rs | 0 {src => saltwater-parser}/analyze/init.rs | 0 {src => saltwater-parser}/analyze/mod.rs | 0 {src => saltwater-parser}/analyze/stmt.rs | 0 {src => saltwater-parser}/arch/mod.rs | 4 +- saltwater-parser/arch/x64.rs | 19 ++ {src => saltwater-parser}/data/ast.rs | 0 {src => saltwater-parser}/data/error.rs | 8 +- {src => saltwater-parser}/data/hir.rs | 5 +- {src => saltwater-parser}/data/lex.rs | 0 saltwater-parser/data/mod.rs | 263 +++++++++++++++++++ {src => saltwater-parser}/data/types.rs | 2 +- {src => saltwater-parser}/fold.rs | 2 +- {src => saltwater-parser}/intern.rs | 0 {src => saltwater-parser}/lex/cpp.rs | 0 {src => saltwater-parser}/lex/files.rs | 0 {src => saltwater-parser}/lex/mod.rs | 0 {src => saltwater-parser}/lex/replace.rs | 0 {src => saltwater-parser}/lex/tests.rs | 0 {src => saltwater-parser}/lib.rs | 197 +------------- {src => saltwater-parser}/macros.rs | 2 + {src => saltwater-parser}/parse/decl.rs | 0 {src => saltwater-parser}/parse/expr.rs | 0 {src => saltwater-parser}/parse/mod.rs | 0 {src => saltwater-parser}/parse/stmt.rs | 0 src/{data => }/R2D2-Scream.ogg | Bin src/arch/x64.rs | 17 -- src/data/mod.rs | 113 -------- src/main.rs | 18 +- 36 files changed, 572 insertions(+), 634 deletions(-) create mode 100644 saltwater-codegen/Cargo.toml rename {src/ir => saltwater-codegen}/expr.rs (99%) rename src/ir/mod.rs => saltwater-codegen/lib.rs (73%) rename {src/ir => saltwater-codegen}/static_init.rs (77%) rename {src/ir => saltwater-codegen}/stmt.rs (97%) create mode 100644 saltwater-parser/Cargo.toml rename {src => saltwater-parser}/analyze/expr.rs (100%) rename {src => saltwater-parser}/analyze/init.rs (100%) rename {src => saltwater-parser}/analyze/mod.rs (100%) rename {src => saltwater-parser}/analyze/stmt.rs (100%) rename {src => saltwater-parser}/arch/mod.rs (99%) create mode 100644 saltwater-parser/arch/x64.rs rename {src => saltwater-parser}/data/ast.rs (100%) rename {src => saltwater-parser}/data/error.rs (98%) rename {src => saltwater-parser}/data/hir.rs (99%) rename {src => saltwater-parser}/data/lex.rs (100%) create mode 100644 saltwater-parser/data/mod.rs rename {src => saltwater-parser}/data/types.rs (99%) rename {src => saltwater-parser}/fold.rs (99%) rename {src => saltwater-parser}/intern.rs (100%) rename {src => saltwater-parser}/lex/cpp.rs (100%) rename {src => saltwater-parser}/lex/files.rs (100%) rename {src => saltwater-parser}/lex/mod.rs (100%) rename {src => saltwater-parser}/lex/replace.rs (100%) rename {src => saltwater-parser}/lex/tests.rs (100%) rename {src => saltwater-parser}/lib.rs (56%) rename {src => saltwater-parser}/macros.rs (97%) rename {src => saltwater-parser}/parse/decl.rs (100%) rename {src => saltwater-parser}/parse/expr.rs (100%) rename {src => saltwater-parser}/parse/mod.rs (100%) rename {src => saltwater-parser}/parse/stmt.rs (100%) rename src/{data => }/R2D2-Scream.ogg (100%) delete mode 100644 src/arch/x64.rs delete mode 100644 src/data/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 70e0769b..cdd25923 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,27 +21,28 @@ default-run = "swcc" documentation = "https://docs.rs/saltwater" [dependencies] -lazy_static = "1" -ansi_term = { version = "0.12", optional = true } -cranelift = { version = "0.66", optional = true } -cranelift-module = { version = "0.66", optional = true } -cranelift-object = { version = "0.66", optional = true } -cranelift-simplejit = { version = "0.66", optional = true } -hexponent = "0.3" -thiserror = "^1.0.10" -target-lexicon = "0.10" -tempfile = { version = "3", optional = true } -pico-args = { version = "0.3.4", optional = true, features = ["short-space-opt"] } -lasso = "0.2" -codespan = { version = ">=0.9.5", default-features = false } +#lazy_static = "1" +ansi_term = "0.12" +arcstr = "0.2" +#cranelift = { version = "0.66", optional = true } +#cranelift-module = { version = "0.66", optional = true } +#cranelift-object = { version = "0.66", optional = true } +#cranelift-simplejit = { version = "0.66", optional = true } +#hexponent = "0.3" +#thiserror = "^1.0.10" +#target-lexicon = "0.10" +tempfile = "3" +pico-args = { version = "0.3.4", features = ["short-space-opt"] } +#lasso = "0.2" +#codespan = { version = ">=0.9.5", default-features = false } color-backtrace = { version = "0.4", default-features = false, optional = true } -counter = "0.4" -atty = { version = "0.2", default-features = false, optional = true } +#counter = "0.4" +atty = { version = "0.2", default-features = false } git-testament = { version = "0.1", optional = true } rand = { version = "0.7", optional = true } rodio = { version = "0.11.0", optional = true } -arcstr = "0.2.2" -time = "0.2" +saltwater-parser = { path = "saltwater-parser" } +saltwater-codegen = { path = "saltwater-codegen" } [dev-dependencies] env_logger = { version = "0.7", default-features = false } @@ -52,11 +53,8 @@ proptest = "^0.9.6" proptest-derive = "0.1" [features] -default = ["cc", "codegen", "color-backtrace"] -# The `swcc` binary -cc = ["ansi_term", "tempfile", "pico-args", "codegen", "atty"] -codegen = ["cranelift", "cranelift-module", "cranelift-object"] -jit = ["codegen", "cranelift-simplejit"] +default = ["color-backtrace"] +#codegen = ["cranelift", "cranelift-module", "cranelift-object"] salty = ["rand", "rodio"] # for internal use _test_headers = [] @@ -64,7 +62,6 @@ _test_headers = [] [[bin]] name = "swcc" path = "src/main.rs" -required-features = ["cc"] [[bench]] name = "examples" diff --git a/saltwater-codegen/Cargo.toml b/saltwater-codegen/Cargo.toml new file mode 100644 index 00000000..2d55855b --- /dev/null +++ b/saltwater-codegen/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "saltwater-codegen" +version = "0.1.0" +authors = ["Joshua Nelson "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +path = "lib.rs" + +[dependencies] +cranelift = "0.66" +cranelift-module = "0.66" +cranelift-object = "0.66" +cranelift-simplejit = { version = "0.66", optional = true } +target-lexicon = "0.10" + +lazy_static = "1" + +saltwater-parser = { path = "../saltwater-parser", features = ["codegen"] } + +[features] +jit = ["cranelift-simplejit"] diff --git a/src/ir/expr.rs b/saltwater-codegen/expr.rs similarity index 99% rename from src/ir/expr.rs rename to saltwater-codegen/expr.rs index 6717542e..f90ebedc 100644 --- a/src/ir/expr.rs +++ b/saltwater-codegen/expr.rs @@ -3,10 +3,10 @@ use cranelift::prelude::{FunctionBuilder, InstBuilder, Type as IrType, Value as use cranelift_module::Backend; use super::{Compiler, Id}; -use crate::data::*; -use crate::data::{ - hir::{BinaryOp, Expr, ExprType, LiteralValue, Symbol, Variable}, +use saltwater_parser::data::{ + hir::{self, BinaryOp, Expr, ExprType, LiteralValue, Symbol, Variable}, lex::ComparisonToken, + * }; type IrResult = CompileResult; @@ -504,7 +504,7 @@ impl Compiler { args: Vec, builder: &mut FunctionBuilder, ) -> IrResult { - use crate::data::hir::Qualifiers; + use hir::Qualifiers; use cranelift::codegen::ir::{AbiParam, ArgumentPurpose}; let mut ftype = match ctype { diff --git a/src/ir/mod.rs b/saltwater-codegen/lib.rs similarity index 73% rename from src/ir/mod.rs rename to saltwater-codegen/lib.rs index 5166913b..3fd798ca 100644 --- a/src/ir/mod.rs +++ b/saltwater-codegen/lib.rs @@ -14,43 +14,34 @@ mod expr; mod static_init; mod stmt; -use std::collections::{HashMap, VecDeque}; +use std::collections::HashMap; use std::convert::TryFrom; +use std::path::Path; -use crate::arch::{CHAR_BIT, PTR_SIZE, SIZE_T, TARGET}; -use crate::data::lex::ComparisonToken; +use saltwater_parser::{Program, Opt}; +use saltwater_parser::arch::TARGET; use cranelift::codegen::{ self, ir::{ - condcodes::{FloatCC, IntCC}, entities::StackSlot, function::Function, stackslot::{StackSlotData, StackSlotKind}, - types::{self, Type as IrType}, - AbiParam, ArgumentPurpose, ExternalName, InstBuilder, MemFlags, Signature, + ExternalName, InstBuilder, MemFlags, }, - isa::{CallConv, TargetIsa}, + isa::TargetIsa, settings::{self, Configurable, Flags}, }; use cranelift::frontend::Switch; use cranelift::prelude::{Block, FunctionBuilder, FunctionBuilderContext}; use cranelift_module::{self, Backend, DataId, FuncId, Linkage, Module}; use cranelift_object::{ObjectBackend, ObjectBuilder}; -use lazy_static::lazy_static; -use crate::data::{ +use saltwater_parser::data::{ hir::{Declaration, Initializer, Stmt, Symbol}, types::FunctionType, StorageClass, *, }; -// TODO: make this const when const_if_match is stabilized -// TODO: see https://github.com/rust-lang/rust/issues/49146 -lazy_static! { - /// The calling convention for the current target. - pub(crate) static ref CALLING_CONVENTION: CallConv = CallConv::triple_default(&TARGET); -} - pub(crate) fn get_isa(jit: bool) -> Box { let mut flags_builder = cranelift::codegen::settings::builder(); // `simplejit` requires non-PIC code @@ -105,48 +96,15 @@ struct Compiler { error_handler: ErrorHandler, } +/* /// Compile a program from a high level IR to a Cranelift Module pub(crate) fn compile( module: Module, program: Vec>, debug: bool, ) -> (Result, CompileError>, VecDeque) { - // really we'd like to have all errors but that requires a refactor - let mut err = None; - let mut compiler = Compiler::new(module, debug); - for decl in program { - let meta = decl.data.symbol.get(); - if let StorageClass::Typedef = meta.storage_class { - continue; - } - let current = match &meta.ctype { - Type::Function(func_type) => match decl.data.init { - Some(Initializer::FunctionBody(stmts)) => { - compiler.compile_func(decl.data.symbol, &func_type, stmts, decl.location) - } - None => compiler.declare_func(decl.data.symbol, false).map(|_| ()), - _ => unreachable!("functions can only be initialized by a FunctionBody"), - }, - Type::Void | Type::Error => unreachable!("parser let an incomplete type through"), - _ => { - if let Some(Initializer::FunctionBody(_)) = &decl.data.init { - unreachable!("only functions should have a function body") - } - compiler.store_static(decl.data.symbol, decl.data.init, decl.location) - } - }; - if let Err(e) = current { - err = Some(e); - break; - } - } - let warns = compiler.error_handler.warnings; - if let Some(err) = err { - (Err(err), warns) - } else { - (Ok(compiler.module), warns) - } } +*/ impl Compiler { fn new(module: Module, debug: bool) -> Compiler { @@ -173,7 +131,7 @@ impl Compiler { // 3. should always declare `id` as export or local. // 2. and 4. should be a no-op. fn declare_func(&mut self, symbol: Symbol, is_definition: bool) -> CompileResult { - use crate::get_str; + use saltwater_parser::get_str; if !is_definition { // case 2 and 4 if let Some(Id::Function(func_id)) = self.declarations.get(&symbol) { @@ -395,133 +353,99 @@ impl Compiler { } } -impl FunctionType { - fn has_params(&self) -> bool { - !(self.params.len() == 1 && self.params[0].get().ctype == Type::Void) - } +pub type Product = ::Product; - /// Generate the IR function signature for `self` - pub fn signature(&self, isa: &dyn TargetIsa) -> Signature { - let mut params = if self.params.len() == 1 && self.params[0].get().ctype == Type::Void { - // no arguments - Vec::new() - } else { - self.params - .iter() - .map(|param| AbiParam::new(param.get().ctype.as_ir_type())) - .collect() - }; - if self.varargs { - let al = isa - .register_info() - .parse_regunit("rax") - .expect("x86 should have an rax register"); - params.push(AbiParam::special_reg( - types::I8, - ArgumentPurpose::Normal, - al, - )); - } - let return_type = if !self.should_return() { - vec![] - } else { - vec![AbiParam::new(self.return_type.as_ir_type())] - }; - Signature { - call_conv: *CALLING_CONVENTION, - params, - returns: return_type, - } - } -} +/// Compile and return the declarations and warnings. +pub fn compile(module: Module, buf: &str, opt: Opt) -> Program> { + use saltwater_parser::{vec_deque, check_semantics}; -impl ComparisonToken { - pub fn to_int_compare(self, signed: bool) -> IntCC { - use ComparisonToken::*; - match (self, signed) { - (Less, true) => IntCC::SignedLessThan, - (Less, false) => IntCC::UnsignedLessThan, - (LessEqual, true) => IntCC::SignedLessThanOrEqual, - (LessEqual, false) => IntCC::UnsignedLessThanOrEqual, - (Greater, true) => IntCC::SignedGreaterThan, - (Greater, false) => IntCC::UnsignedGreaterThan, - (GreaterEqual, true) => IntCC::SignedGreaterThanOrEqual, - (GreaterEqual, false) => IntCC::UnsignedGreaterThanOrEqual, - (EqualEqual, _) => IntCC::Equal, - (NotEqual, _) => IntCC::NotEqual, + let debug_asm = opt.debug_asm; + let mut program = check_semantics(buf, opt); + let hir = match program.result { + Ok(hir) => hir, + Err(err) => { + return Program { + result: Err(err), + warnings: program.warnings, + files: program.files, + } } - } - pub fn to_float_compare(self) -> FloatCC { - use ComparisonToken::*; - match self { - Less => FloatCC::LessThan, - LessEqual => FloatCC::LessThanOrEqual, - Greater => FloatCC::GreaterThan, - GreaterEqual => FloatCC::GreaterThanOrEqual, - EqualEqual => FloatCC::Equal, - NotEqual => FloatCC::NotEqual, + }; + // really we'd like to have all errors but that requires a refactor + let mut err = None; + let mut compiler = Compiler::new(module, debug_asm); + for decl in hir { + let meta = decl.data.symbol.get(); + if let StorageClass::Typedef = meta.storage_class { + continue; } - } -} - -use std::convert::TryInto; -impl Type { - /// Return an IR integer type large enough to contain a pointer. - pub fn ptr_type() -> IrType { - IrType::int(CHAR_BIT * PTR_SIZE).expect("pointer size should be valid") - } - /// Return an IR type which can represent this C type - pub fn as_ir_type(&self) -> IrType { - use Type::*; - - match self { - // Integers - Bool => types::B1, - Char(_) | Short(_) | Int(_) | Long(_) | Pointer(_, _) | Enum(_, _) => { - let int_size = SIZE_T::from(CHAR_BIT) - * self - .sizeof() - .expect("integers should always have a valid size"); - IrType::int(int_size.try_into().unwrap_or_else(|_| { - panic!( - "integers should never have a size larger than {}", - i16::max_value() - ) - })) - .unwrap_or_else(|| panic!("unsupported size for IR: {}", int_size)) + let current = match &meta.ctype { + Type::Function(func_type) => match decl.data.init { + Some(Initializer::FunctionBody(stmts)) => { + compiler.compile_func(decl.data.symbol, &func_type, stmts, decl.location) + } + None => compiler.declare_func(decl.data.symbol, false).map(|_| ()), + _ => unreachable!("functions can only be initialized by a FunctionBody"), + }, + Type::Void | Type::Error => unreachable!("parser let an incomplete type through"), + _ => { + if let Some(Initializer::FunctionBody(_)) = &decl.data.init { + unreachable!("only functions should have a function body") + } + compiler.store_static(decl.data.symbol, decl.data.init, decl.location) } - - // Floats - // TODO: this is hard-coded for x64 - Float => types::F32, - Double => types::F64, - - // Aggregates - // arrays and functions decay to pointers - Function(_) | Array(_, _) => IrType::int(PTR_SIZE * CHAR_BIT) - .unwrap_or_else(|| panic!("unsupported size of IR: {}", PTR_SIZE)), - // void cannot be loaded or stored - _ => types::INVALID, + }; + if let Err(e) = current { + err = Some(e); + break; } } - fn member_offset(&self, member: InternedStr) -> Result { - match self { - Type::Struct(stype) => Ok(stype.offset(member)), - Type::Union(_) => Ok(0), - _ => Err(()), - } + let warns = compiler.error_handler.warnings; + let (result, ir_warnings) = if let Some(err) = err { + (Err(err), warns) + } else { + (Ok(compiler.module), warns) + }; + program.warnings.extend(ir_warnings); + Program { + result: result.map_err(|errs| vec_deque![errs]), + warnings: program.warnings, + files: program.files, } } -impl CompileError { - fn semantic(err: Locatable) -> Self { - Self::from(err) - } +pub fn assemble(product: Product, output: &Path) -> Result<(), saltwater_parser::Error> { + use std::io::{self, Write}; + use std::fs::File; + + let bytes = product.emit().map_err(saltwater_parser::Error::Platform)?; + File::create(output)? + .write_all(&bytes) + .map_err(io::Error::into) } -impl FunctionType { - fn should_return(&self) -> bool { - *self.return_type != Type::Void +pub fn link(obj_file: &Path, output: &Path) -> Result<(), std::io::Error> { + use std::io::{Error, ErrorKind}; + use std::process::Command; + + // link the .o file using host linker + let status = Command::new("cc") + .args(&[&obj_file, Path::new("-o"), output]) + .status() + .map_err(|err| { + if err.kind() == ErrorKind::NotFound { + Error::new( + ErrorKind::NotFound, + "could not find host cc (for linking). Is it on your PATH?", + ) + } else { + err + } + })?; + if !status.success() { + Err(Error::new(ErrorKind::Other, "linking program failed")) + } else { + Ok(()) } } diff --git a/src/ir/static_init.rs b/saltwater-codegen/static_init.rs similarity index 77% rename from src/ir/static_init.rs rename to saltwater-codegen/static_init.rs index b69db69e..a89223f8 100644 --- a/src/ir/static_init.rs +++ b/saltwater-codegen/static_init.rs @@ -7,13 +7,14 @@ use cranelift::codegen::ir::types; use cranelift_module::{Backend, DataContext, DataId, Linkage}; use super::{Compiler, Id}; -use crate::arch::{PTR_SIZE, TARGET}; -use crate::data::*; -use crate::data::{ +use saltwater_parser::arch::{PTR_SIZE, TARGET}; +use saltwater_parser::data::{ hir::{Expr, ExprType, Initializer, LiteralValue, Symbol}, types::ArrayType, StorageClass, + * }; +use saltwater_parser::const_assert; const_assert!(PTR_SIZE <= std::usize::MAX as u16); const ZERO_PTR: [u8; PTR_SIZE as usize] = [0; PTR_SIZE as usize]; @@ -52,7 +53,7 @@ impl Compiler { init: Option, location: Location, ) -> CompileResult<()> { - use crate::get_str; + use saltwater_parser::get_str; let metadata = symbol.get(); if let StorageClass::Typedef = metadata.storage_class { return Ok(()); @@ -74,7 +75,7 @@ impl Compiler { // struct that was declared but never used return Ok(()); } - let linkage = metadata.storage_class.try_into().map_err(err_closure)?; + let linkage = linkage_from_storage_class(metadata.storage_class).map_err(err_closure)?; let id = self .module .declare_data( @@ -211,7 +212,7 @@ impl Compiler { }, ExprType::Literal(token) => { let bytes = - token.into_bytes(&expr.ctype, &expr.location, &mut self.error_handler)?; + into_bytes(token, &expr.ctype, &expr.location, &mut self.error_handler)?; buf.copy_from_slice(&bytes); } _ => semantic_err!( @@ -350,95 +351,90 @@ impl Compiler { } } -impl LiteralValue { - fn into_bytes( - self, - ctype: &Type, - location: &Location, - error_handler: &mut ErrorHandler, - ) -> CompileResult> { - let ir_type = ctype.as_ir_type(); - let big_endian = TARGET - .endianness() - .expect("target should be big or little endian") - == target_lexicon::Endianness::Big; +fn into_bytes( + value: LiteralValue, + ctype: &Type, + location: &Location, + error_handler: &mut ErrorHandler, +) -> CompileResult> { + let ir_type = ctype.as_ir_type(); + let big_endian = TARGET + .endianness() + .expect("target should be big or little endian") + == target_lexicon::Endianness::Big; - match self { - LiteralValue::Int(i) => Ok(match ir_type { - types::I8 => bytes!( - cast!(i, i64, i8, &ctype, *location, error_handler), - big_endian - ), - types::I16 => bytes!( - cast!(i, i64, i16, &ctype, *location, error_handler), - big_endian - ), - types::I32 => bytes!( - cast!(i, i64, i32, &ctype, *location, error_handler), - big_endian - ), - types::I64 => bytes!(i, big_endian), - x => unreachable!(format!( - "ir_type {} for integer {} is not of integer type", - x, i - )), - }), - LiteralValue::UnsignedInt(i) => Ok(match ir_type { - types::I8 => bytes!( - cast!(i, u64, u8, &ctype, *location, error_handler), - big_endian - ), - types::I16 => bytes!( - cast!(i, u64, u16, &ctype, *location, error_handler), - big_endian - ), - types::I32 => bytes!( - cast!(i, u64, u32, &ctype, *location, error_handler), - big_endian - ), - types::I64 => bytes!(i, big_endian), - x => unreachable!(format!( - "ir_type {} for integer {} is not of integer type", - x, i - )), - }), - LiteralValue::Float(f) => Ok(match ir_type { - types::F32 => { - let cast = f as f32; - if (f64::from(cast) - f).abs() >= std::f64::EPSILON { - let warning = format!( - "conversion from double to float loses precision ({} is different from {} by more than DBL_EPSILON ({}))", - f, std::f64::EPSILON, f64::from(cast) - ); - error_handler.warn(&warning, *location); - } - let float_as_int = cast.to_bits(); - bytes!(float_as_int, big_endian) + match value { + LiteralValue::Int(i) => Ok(match ir_type { + types::I8 => bytes!( + cast!(i, i64, i8, &ctype, *location, error_handler), + big_endian + ), + types::I16 => bytes!( + cast!(i, i64, i16, &ctype, *location, error_handler), + big_endian + ), + types::I32 => bytes!( + cast!(i, i64, i32, &ctype, *location, error_handler), + big_endian + ), + types::I64 => bytes!(i, big_endian), + x => unreachable!(format!( + "ir_type {} for integer {} is not of integer type", + x, i + )), + }), + LiteralValue::UnsignedInt(i) => Ok(match ir_type { + types::I8 => bytes!( + cast!(i, u64, u8, &ctype, *location, error_handler), + big_endian + ), + types::I16 => bytes!( + cast!(i, u64, u16, &ctype, *location, error_handler), + big_endian + ), + types::I32 => bytes!( + cast!(i, u64, u32, &ctype, *location, error_handler), + big_endian + ), + types::I64 => bytes!(i, big_endian), + x => unreachable!(format!( + "ir_type {} for integer {} is not of integer type", + x, i + )), + }), + LiteralValue::Float(f) => Ok(match ir_type { + types::F32 => { + let cast = f as f32; + if (f64::from(cast) - f).abs() >= std::f64::EPSILON { + let warning = format!( + "conversion from double to float loses precision ({} is different from {} by more than DBL_EPSILON ({}))", + f, std::f64::EPSILON, f64::from(cast) + ); + error_handler.warn(&warning, *location); } - types::F64 => bytes!(f.to_bits(), big_endian), - x => unreachable!(format!( - "ir_type {} for float {} is not of integer type", - x, f - )), - }), - LiteralValue::Str(string) => Ok(string.into_boxed_slice()), - LiteralValue::Char(c) => Ok(Box::new([c])), - } + let float_as_int = cast.to_bits(); + bytes!(float_as_int, big_endian) + } + types::F64 => bytes!(f.to_bits(), big_endian), + x => unreachable!(format!( + "ir_type {} for float {} is not of integer type", + x, f + )), + }), + LiteralValue::Str(string) => Ok(string.into_boxed_slice()), + LiteralValue::Char(c) => Ok(Box::new([c])), } } -impl TryFrom for Linkage { - type Error = String; - // INVARIANT: this should be the linkage for an object, not for a function - fn try_from(sc: StorageClass) -> Result { - match sc { - StorageClass::Extern => Ok(Linkage::Import), - StorageClass::Static => Ok(Linkage::Local), - StorageClass::Auto => Ok(Linkage::Export), - StorageClass::Register => { - Err(format!("illegal storage class {} for global variable", sc)) - } - StorageClass::Typedef => unreachable!("typedefs should be handled by parser"), +// INVARIANT: this should be the linkage for an object, not for a function +fn linkage_from_storage_class(sc: StorageClass) -> Result { + match sc { + StorageClass::Extern => Ok(Linkage::Import), + StorageClass::Static => Ok(Linkage::Local), + StorageClass::Auto => Ok(Linkage::Export), + StorageClass::Register => { + Err(format!("illegal storage class {} for global variable", sc)) } + StorageClass::Typedef => unreachable!("typedefs should be handled by parser"), } } diff --git a/src/ir/stmt.rs b/saltwater-codegen/stmt.rs similarity index 97% rename from src/ir/stmt.rs rename to saltwater-codegen/stmt.rs index 1ef234fa..990991db 100644 --- a/src/ir/stmt.rs +++ b/saltwater-codegen/stmt.rs @@ -4,7 +4,7 @@ use cranelift::prelude::{Block, FunctionBuilder, InstBuilder}; use cranelift_module::Backend; use super::Compiler; -use crate::data::{ +use saltwater_parser::data::{ hir::{Expr, Stmt, StmtType}, *, }; @@ -25,7 +25,7 @@ impl Compiler { stmt: Stmt, builder: &mut FunctionBuilder, ) -> CompileResult<()> { - if builder.is_filled() && !stmt.data.is_jump_target() { + if builder.is_filled() && !is_jump_target(&stmt.data) { return Err(stmt.location.error(SemanticError::UnreachableStatement)); } match stmt.data { @@ -374,11 +374,9 @@ impl Compiler { } } -impl StmtType { - fn is_jump_target(&self) -> bool { - match self { - StmtType::Case(_, _) | StmtType::Default(_) | StmtType::Label(_, _) => true, - _ => false, - } +fn is_jump_target(stmt: &StmtType) -> bool { + match stmt { + StmtType::Case(_, _) | StmtType::Default(_) | StmtType::Label(_, _) => true, + _ => false, } } diff --git a/saltwater-parser/Cargo.toml b/saltwater-parser/Cargo.toml new file mode 100644 index 00000000..99569912 --- /dev/null +++ b/saltwater-parser/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "saltwater-parser" +version = "0.1.0" +authors = ["Joshua Nelson "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +path = "lib.rs" + +[dependencies] +lazy_static = "1" +arcstr = "0.2.2" +ansi_term = { version = "0.12", optional = true } +hexponent = "0.3" +thiserror = "^1.0.10" +target-lexicon = "0.10" +tempfile = { version = "3", optional = true } +pico-args = { version = "0.3.4", optional = true, features = ["short-space-opt"] } +lasso = "0.2" +codespan = { version = ">=0.9.5", default-features = false } +color-backtrace = { version = "0.4", default-features = false, optional = true } +counter = "0.4" +atty = { version = "0.2", default-features = false, optional = true } +git-testament = { version = "0.1", optional = true } +rand = { version = "0.7", optional = true } +rodio = { version = "0.11.0", optional = true } +shared_str = "0.1.1" +time = "0.2" +cranelift-codegen = { version = "0.66", optional = true } +cranelift-object = { version = "0.66", optional = true } + +[features] +codegen = ["cranelift-codegen", "cranelift-object"] diff --git a/src/analyze/expr.rs b/saltwater-parser/analyze/expr.rs similarity index 100% rename from src/analyze/expr.rs rename to saltwater-parser/analyze/expr.rs diff --git a/src/analyze/init.rs b/saltwater-parser/analyze/init.rs similarity index 100% rename from src/analyze/init.rs rename to saltwater-parser/analyze/init.rs diff --git a/src/analyze/mod.rs b/saltwater-parser/analyze/mod.rs similarity index 100% rename from src/analyze/mod.rs rename to saltwater-parser/analyze/mod.rs diff --git a/src/analyze/stmt.rs b/saltwater-parser/analyze/stmt.rs similarity index 100% rename from src/analyze/stmt.rs rename to saltwater-parser/analyze/stmt.rs diff --git a/src/arch/mod.rs b/saltwater-parser/arch/mod.rs similarity index 99% rename from src/arch/mod.rs rename to saltwater-parser/arch/mod.rs index de84a539..646facb3 100644 --- a/src/arch/mod.rs +++ b/saltwater-parser/arch/mod.rs @@ -23,10 +23,10 @@ const CHAR_SIZE: u16 = 1; /// Traditionaly, the target triple uses this format: `--` /// The target triple is represented as a struct and contains additional /// information like ABI and endianness. -pub(crate) const TARGET: Triple = Triple::host(); +pub const TARGET: Triple = Triple::host(); mod x64; -pub(crate) use x64::*; +pub use x64::*; impl StructType { /// Get the offset of the given struct member. diff --git a/saltwater-parser/arch/x64.rs b/saltwater-parser/arch/x64.rs new file mode 100644 index 00000000..69bc8b3c --- /dev/null +++ b/saltwater-parser/arch/x64.rs @@ -0,0 +1,19 @@ +//! https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models +#![allow(missing_docs)] + +#[allow(non_camel_case_types)] +pub type SIZE_T = u64; +#[allow(dead_code)] +pub const SIZE_MAX: SIZE_T = SIZE_T::max_value(); + +pub const FLOAT_SIZE: u16 = 4; +pub const DOUBLE_SIZE: u16 = 8; + +pub const LONG_SIZE: u16 = 8; +pub const INT_SIZE: u16 = 4; +pub const SHORT_SIZE: u16 = 2; +pub const BOOL_SIZE: u16 = 1; + +pub const PTR_SIZE: u16 = 8; + +pub const CHAR_BIT: u16 = 8; // number of bits in a byte diff --git a/src/data/ast.rs b/saltwater-parser/data/ast.rs similarity index 100% rename from src/data/ast.rs rename to saltwater-parser/data/ast.rs diff --git a/src/data/error.rs b/saltwater-parser/data/error.rs similarity index 98% rename from src/data/error.rs rename to saltwater-parser/data/error.rs index fef5b59e..1bede8d1 100644 --- a/src/data/error.rs +++ b/saltwater-parser/data/error.rs @@ -16,9 +16,9 @@ pub type CompileWarning = Locatable; /// part of the compiler, this cannot be represented well with Rust's normal /// `Result`. #[derive(Clone, Debug, PartialEq)] -pub(crate) struct ErrorHandler { +pub struct ErrorHandler { errors: VecDeque>, - pub(crate) warnings: VecDeque, + pub warnings: VecDeque, } // Can't be derived because the derive mistakenly puts a bound of T: Default @@ -53,7 +53,7 @@ impl ErrorHandler { } /// Shortcut for adding a warning - pub(crate) fn warn>(&mut self, warning: W, location: Location) { + pub fn warn>(&mut self, warning: W, location: Location) { self.warnings.push_back(location.with(warning.into())); } @@ -291,7 +291,7 @@ pub enum SemanticError { // TODO: this error should happen way before codegen #[cfg(feature = "codegen")] #[error("redeclaration of label {0}")] - LabelRedeclaration(cranelift::prelude::Block), + LabelRedeclaration(cranelift_codegen::ir::entities::Block), #[error("use of undeclared label {0}")] UndeclaredLabel(InternedStr), diff --git a/src/data/hir.rs b/saltwater-parser/data/hir.rs similarity index 99% rename from src/data/hir.rs rename to saltwater-parser/data/hir.rs index 4f2e4912..3c0a9f53 100644 --- a/src/data/hir.rs +++ b/saltwater-parser/data/hir.rs @@ -115,7 +115,7 @@ impl Symbol { } impl Variable { - pub(crate) fn insert(self) -> Symbol { + pub fn insert(self) -> Symbol { SYMBOL_TABLE.with(|store| store.borrow_mut().insert(self)) } } @@ -242,7 +242,8 @@ impl Qualifiers { pub(crate) fn has_func_qualifiers(self) -> bool { self.func.inline || self.func.no_return } - pub(crate) const NONE: Qualifiers = Qualifiers { + // TODO: this should just be a Default + pub const NONE: Qualifiers = Qualifiers { c_const: false, volatile: false, func: FunctionQualifiers { diff --git a/src/data/lex.rs b/saltwater-parser/data/lex.rs similarity index 100% rename from src/data/lex.rs rename to saltwater-parser/data/lex.rs diff --git a/saltwater-parser/data/mod.rs b/saltwater-parser/data/mod.rs new file mode 100644 index 00000000..d59e513b --- /dev/null +++ b/saltwater-parser/data/mod.rs @@ -0,0 +1,263 @@ +pub mod ast; +pub mod error; +pub mod hir; +pub mod lex; +pub mod types; + +pub use crate::intern::InternedStr; +pub use error::{CompileError, CompileResult, CompileWarning, Error, ErrorHandler, SemanticError, SyntaxError}; +pub use hir::LiteralValue; +pub use lex::{LiteralToken, Locatable, Location, Token}; +pub use types::Type; +pub use types::{StructRef, StructType}; + +use std::convert::TryFrom; +use std::fmt::{self, Display}; + +use lex::Keyword; + +// used by both `ast` and `hir` +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum StorageClass { + Static, + Extern, + Auto, + Register, + Typedef, +} + +// helper functions for `Display` impls +fn joined, T: ToString>(it: I, delim: &str) -> String { + it.into_iter() + .map(|s| s.to_string()) + .collect::>() + .join(delim) +} + +fn joined_locatable<'a, I: IntoIterator>, T: ToString + 'a>( + it: I, + delim: &str, +) -> String { + joined(it.into_iter().map(|s| s.data.to_string()), delim) +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Radix { + Binary, + Octal, + Decimal, + Hexadecimal, +} + +impl Radix { + pub fn as_u8(self) -> u8 { + match self { + Radix::Binary => 2, + Radix::Octal => 8, + Radix::Decimal => 10, + Radix::Hexadecimal => 16, + } + } +} + +impl Display for Radix { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let word = match self { + Radix::Binary => "binary", + Radix::Octal => "octal", + Radix::Decimal => "decimal", + Radix::Hexadecimal => "hexadecimal", + }; + write!(f, "{}", word) + } +} + +impl TryFrom for Radix { + type Error = (); + fn try_from(int: u32) -> Result { + match int { + 2 => Ok(Radix::Binary), + 8 => Ok(Radix::Octal), + 10 => Ok(Radix::Decimal), + 16 => Ok(Radix::Hexadecimal), + _ => Err(()), + } + } +} + +#[cfg(test)] +mod tests { + use crate::analyze::test::analyze; + use crate::Parser; + + #[test] + fn type_display() { + let types = [ + "int", + "int *", + "int [1][2][3]", + "char *(*)(float)", + "short *(*)[1][2][3]", + "_Bool", + "struct s", + ]; + for ty in types.iter() { + let parsed_ty = analyze(ty, Parser::type_name, |a, b| { + a.parse_typename_test(b.data, b.location) + }) + .unwrap(); + assert_eq!(&parsed_ty.to_string(), *ty); + } + } +} + +#[cfg(feature = "codegen")] +mod codegen_impls { + use crate::arch::*; + use crate::data::{lex::{ComparisonToken, Locatable}, Type, types::FunctionType, error::CompileError}; + use crate::intern::InternedStr; + use cranelift_codegen::ir::{ + AbiParam, ArgumentPurpose, + condcodes::{FloatCC, IntCC}, + types::{self, Type as IrType}, + }; + use cranelift_codegen::ir::Signature; + use cranelift_codegen::isa::{CallConv, TargetIsa}; + + // TODO: make this const when const_if_match is stabilized + // TODO: see https://github.com/rust-lang/rust/issues/49146 + lazy_static::lazy_static! { + /// The calling convention for the current target. + pub(crate) static ref CALLING_CONVENTION: CallConv = CallConv::triple_default(&TARGET); + } + + impl FunctionType { + pub fn should_return(&self) -> bool { + *self.return_type != Type::Void + } + } + + impl Type { + /// Return an IR integer type large enough to contain a pointer. + pub fn ptr_type() -> IrType { + IrType::int(CHAR_BIT * PTR_SIZE).expect("pointer size should be valid") + } + /// Return an IR type which can represent this C type + pub fn as_ir_type(&self) -> IrType { + use Type::*; + use std::convert::TryInto; + + match self { + // Integers + Bool => types::B1, + Char(_) | Short(_) | Int(_) | Long(_) | Pointer(_, _) | Enum(_, _) => { + let int_size = SIZE_T::from(CHAR_BIT) + * self + .sizeof() + .expect("integers should always have a valid size"); + IrType::int(int_size.try_into().unwrap_or_else(|_| { + panic!( + "integers should never have a size larger than {}", + i16::max_value() + ) + })) + .unwrap_or_else(|| panic!("unsupported size for IR: {}", int_size)) + } + + // Floats + // TODO: this is hard-coded for x64 + Float => types::F32, + Double => types::F64, + + // Aggregates + // arrays and functions decay to pointers + Function(_) | Array(_, _) => IrType::int(PTR_SIZE * CHAR_BIT) + .unwrap_or_else(|| panic!("unsupported size of IR: {}", PTR_SIZE)), + // void cannot be loaded or stored + _ => types::INVALID, + } + } + pub fn member_offset(&self, member: InternedStr) -> Result { + match self { + Type::Struct(stype) => Ok(stype.offset(member)), + Type::Union(_) => Ok(0), + _ => Err(()), + } + } + } + + impl CompileError { + pub fn semantic(err: Locatable) -> Self { + Self::from(err) + } + } + + impl FunctionType { + pub fn has_params(&self) -> bool { + !(self.params.len() == 1 && self.params[0].get().ctype == Type::Void) + } + + /// Generate the IR function signature for `self` + pub fn signature(&self, isa: &dyn TargetIsa) -> Signature { + let mut params = if self.params.len() == 1 && self.params[0].get().ctype == Type::Void { + // no arguments + Vec::new() + } else { + self.params + .iter() + .map(|param| AbiParam::new(param.get().ctype.as_ir_type())) + .collect() + }; + if self.varargs { + let al = isa + .register_info() + .parse_regunit("rax") + .expect("x86 should have an rax register"); + params.push(AbiParam::special_reg( + types::I8, + ArgumentPurpose::Normal, + al, + )); + } + let return_type = if !self.should_return() { + vec![] + } else { + vec![AbiParam::new(self.return_type.as_ir_type())] + }; + Signature { + call_conv: *CALLING_CONVENTION, + params, + returns: return_type, + } + } + } + + impl ComparisonToken { + pub fn to_int_compare(self, signed: bool) -> IntCC { + use ComparisonToken::*; + match (self, signed) { + (Less, true) => IntCC::SignedLessThan, + (Less, false) => IntCC::UnsignedLessThan, + (LessEqual, true) => IntCC::SignedLessThanOrEqual, + (LessEqual, false) => IntCC::UnsignedLessThanOrEqual, + (Greater, true) => IntCC::SignedGreaterThan, + (Greater, false) => IntCC::UnsignedGreaterThan, + (GreaterEqual, true) => IntCC::SignedGreaterThanOrEqual, + (GreaterEqual, false) => IntCC::UnsignedGreaterThanOrEqual, + (EqualEqual, _) => IntCC::Equal, + (NotEqual, _) => IntCC::NotEqual, + } + } + pub fn to_float_compare(self) -> FloatCC { + use ComparisonToken::*; + match self { + Less => FloatCC::LessThan, + LessEqual => FloatCC::LessThanOrEqual, + Greater => FloatCC::GreaterThan, + GreaterEqual => FloatCC::GreaterThanOrEqual, + EqualEqual => FloatCC::Equal, + NotEqual => FloatCC::NotEqual, + } + } + } +} diff --git a/src/data/types.rs b/saltwater-parser/data/types.rs similarity index 99% rename from src/data/types.rs rename to saltwater-parser/data/types.rs index 9061bee4..a844345d 100644 --- a/src/data/types.rs +++ b/saltwater-parser/data/types.rs @@ -183,7 +183,7 @@ pub struct FunctionType { impl Type { /// https://stackoverflow.com/questions/14821936/what-is-a-scalar-object-in-c#14822074 #[inline] - pub(crate) fn is_scalar(&self) -> bool { + pub fn is_scalar(&self) -> bool { use Type::*; match self { Enum(_, _) => true, diff --git a/src/fold.rs b/saltwater-parser/fold.rs similarity index 99% rename from src/fold.rs rename to saltwater-parser/fold.rs index 67a15e26..ae79c263 100644 --- a/src/fold.rs +++ b/saltwater-parser/fold.rs @@ -64,7 +64,7 @@ macro_rules! fold_compare_op { } impl Expr { - pub(crate) fn is_zero(&self) -> bool { + pub fn is_zero(&self) -> bool { if let ExprType::Literal(token) = &self.expr { match *token { Int(i) => i == 0, diff --git a/src/intern.rs b/saltwater-parser/intern.rs similarity index 100% rename from src/intern.rs rename to saltwater-parser/intern.rs diff --git a/src/lex/cpp.rs b/saltwater-parser/lex/cpp.rs similarity index 100% rename from src/lex/cpp.rs rename to saltwater-parser/lex/cpp.rs diff --git a/src/lex/files.rs b/saltwater-parser/lex/files.rs similarity index 100% rename from src/lex/files.rs rename to saltwater-parser/lex/files.rs diff --git a/src/lex/mod.rs b/saltwater-parser/lex/mod.rs similarity index 100% rename from src/lex/mod.rs rename to saltwater-parser/lex/mod.rs diff --git a/src/lex/replace.rs b/saltwater-parser/lex/replace.rs similarity index 100% rename from src/lex/replace.rs rename to saltwater-parser/lex/replace.rs diff --git a/src/lex/tests.rs b/saltwater-parser/lex/tests.rs similarity index 100% rename from src/lex/tests.rs rename to saltwater-parser/lex/tests.rs diff --git a/src/lib.rs b/saltwater-parser/lib.rs similarity index 56% rename from src/lib.rs rename to saltwater-parser/lib.rs index 272adb7a..c30ec07d 100644 --- a/src/lib.rs +++ b/saltwater-parser/lib.rs @@ -8,16 +8,10 @@ use std::collections::{HashMap, VecDeque}; use std::io; -use std::path::{Path, PathBuf}; -use std::process::Command; +use std::path::PathBuf; use std::rc::Rc; pub use codespan; -#[cfg(feature = "codegen")] -use cranelift_module::{Backend, Module}; - -#[cfg(feature = "codegen")] -pub use ir::initialize_aot_module; #[cfg(all(feature = "color-backtrace", not(feature = "cc")))] compile_error!(concat!( @@ -49,8 +43,6 @@ impl AsRef for Source { } pub type Files = codespan::Files; -#[cfg(feature = "codegen")] -pub type Product = ::Product; /// A result which includes all warnings, even for `Err` variants. /// /// If successful, this returns an `Ok(T)`. @@ -85,12 +77,11 @@ pub use parse::Parser; #[macro_use] mod macros; mod analyze; -mod arch; +/// Architecture-specific traits and data +pub mod arch; pub mod data; mod fold; pub mod intern; -#[cfg(feature = "codegen")] -mod ir; mod lex; mod parse; @@ -100,9 +91,11 @@ pub use lex::replace; pub enum Error { #[error("{}", .0.iter().map(|err| err.data.to_string()).collect::>().join("\n"))] Source(VecDeque), + #[cfg(feature = "codegen")] #[error("linking error: {0}")] Platform(cranelift_object::object::write::Error), + #[error("io error: {0}")] IO(#[from] io::Error), } @@ -266,186 +259,6 @@ pub fn check_semantics(buf: &str, opt: Opt) -> Program(module: Module, buf: &str, opt: Opt) -> Program> { - let debug_asm = opt.debug_asm; - let mut program = check_semantics(buf, opt); - let hir = match program.result { - Ok(hir) => hir, - Err(err) => { - return Program { - result: Err(err), - warnings: program.warnings, - files: program.files, - } - } - }; - let (result, ir_warnings) = ir::compile(module, hir, debug_asm); - program.warnings.extend(ir_warnings); - Program { - result: result.map_err(|errs| vec_deque![errs]), - warnings: program.warnings, - files: program.files, - } -} - -#[cfg(feature = "codegen")] -pub fn assemble(product: Product, output: &Path) -> Result<(), Error> { - use io::Write; - use std::fs::File; - - let bytes = product.emit().map_err(Error::Platform)?; - File::create(output)? - .write_all(&bytes) - .map_err(io::Error::into) -} - -pub fn link(obj_file: &Path, output: &Path) -> Result<(), io::Error> { - use std::io::{Error, ErrorKind}; - // link the .o file using host linker - let status = Command::new("cc") - .args(&[&obj_file, Path::new("-o"), output]) - .status() - .map_err(|err| { - if err.kind() == ErrorKind::NotFound { - Error::new( - ErrorKind::NotFound, - "could not find host cc (for linking). Is it on your PATH?", - ) - } else { - err - } - })?; - if !status.success() { - Err(Error::new(ErrorKind::Other, "linking program failed")) - } else { - Ok(()) - } -} - -#[cfg(feature = "jit")] -pub use jit::*; - -#[cfg(feature = "jit")] -mod jit { - use super::*; - use crate::ir::get_isa; - use cranelift_simplejit::{SimpleJITBackend, SimpleJITBuilder}; - use std::convert::TryFrom; - - pub fn initialize_jit_module() -> Module { - let libcall_names = cranelift_module::default_libcall_names(); - Module::new(SimpleJITBuilder::with_isa(get_isa(true), libcall_names)) - } - - /// Structure used to handle compiling C code to memory instead of to disk. - /// - /// You can use [`from_string`] to create a JIT instance. - /// Alternatively, if you don't care about compile warnings, you can use `JIT::try_from` instead. - /// If you already have a `Module`, you can use `JIT::from` to avoid having to `unwrap()`. - /// - /// JIT stands for 'Just In Time' compiled, the way that Java and JavaScript work. - /// - /// [`from_string`]: #method.from_string - pub struct JIT { - module: Module, - } - - impl From> for JIT { - fn from(module: Module) -> Self { - Self { module } - } - } - - impl TryFrom for JIT { - type Error = Error; - fn try_from(source: ArcStr) -> Result { - JIT::from_string(source, Opt::default()).result - } - } - - impl JIT { - /// Compile string and return JITed code. - pub fn from_string>(source: R, opt: Opt) -> Program { - let source = source.into(); - let module = initialize_jit_module(); - let program = compile(module, &source, opt); - let result = match program.result { - Ok(module) => Ok(JIT::from(module)), - Err(errs) => Err(errs.into()), - }; - Program { - result, - warnings: program.warnings, - files: program.files, - } - } - - /// Invoke this function before trying to get access to "new" compiled functions. - pub fn finalize(&mut self) { - self.module.finalize_definitions(); - } - /// Get a compiled function. If this function doesn't exist then `None` is returned, otherwise its address returned. - /// - /// # Panics - /// Panics if function is not compiled (finalized). Try to invoke `finalize` before using `get_compiled_function`. - pub fn get_compiled_function(&mut self, name: &str) -> Option<*const u8> { - use cranelift_module::FuncOrDataId; - - let name = self.module.get_name(name); - if let Some(FuncOrDataId::Func(id)) = name { - Some(self.module.get_finalized_function(id)) - } else { - None - } - } - /// Get compiled static data. If this data doesn't exist then `None` is returned, otherwise its address and size are returned. - pub fn get_compiled_data(&mut self, name: &str) -> Option<(*mut u8, usize)> { - use cranelift_module::FuncOrDataId; - - let name = self.module.get_name(name); - if let Some(FuncOrDataId::Data(id)) = name { - Some(self.module.get_finalized_data(id)) - } else { - None - } - } - /// Given a module, run the `main` function. - /// - /// This automatically calls `self.finalize()`. - /// If `main()` does not exist in the module, returns `None`; otherwise returns the exit code. - /// - /// # Safety - /// This function runs arbitrary C code. - /// It can segfault, access out-of-bounds memory, cause data races, or do anything else C can do. - #[allow(unsafe_code)] - pub unsafe fn run_main(&mut self) -> Option { - self.finalize(); - let main = self.get_compiled_function("main")?; - let args = std::env::args().skip(1); - let argc = args.len() as i32; - // CString should be alive if we want to pass its pointer to another function, - // otherwise this may lead to UB. - let vec_args = args - .map(|string| std::ffi::CString::new(string).unwrap()) - .collect::>(); - // This vec needs to be stored so we aren't passing a pointer to a freed temporary. - let argv = vec_args - .iter() - .map(|cstr| cstr.as_ptr() as *const u8) - .collect::>(); - assert_ne!(main, std::ptr::null()); - // this transmute is safe: this function is finalized (`self.finalize()`) - // and **guaranteed** to be non-null - let main: unsafe extern "C" fn(i32, *const *const u8) -> i32 = - std::mem::transmute(main); - // though transmute is safe, invoking this function is unsafe because we invoke C code. - Some(main(argc, argv.as_ptr() as *const *const u8)) - } - } -} - impl> From for Source { fn from(src: T) -> Self { Self { diff --git a/src/macros.rs b/saltwater-parser/macros.rs similarity index 97% rename from src/macros.rs rename to saltwater-parser/macros.rs index 2da5a26f..7cccbdeb 100644 --- a/src/macros.rs +++ b/saltwater-parser/macros.rs @@ -24,6 +24,7 @@ macro_rules! map { /// ``` /// /// Trailing commas are allowed. +#[macro_export] macro_rules! vec_deque { ($elem:expr; $n:expr) => ({ use std::collections::VecDeque; @@ -38,6 +39,7 @@ macro_rules! vec_deque { /// ensure that a condition is true at compile time /// thanks to https://nikolaivazquez.com/posts/programming/rust-static-assertions/ +#[macro_export] macro_rules! const_assert { ($condition:expr) => { #[deny(const_err)] diff --git a/src/parse/decl.rs b/saltwater-parser/parse/decl.rs similarity index 100% rename from src/parse/decl.rs rename to saltwater-parser/parse/decl.rs diff --git a/src/parse/expr.rs b/saltwater-parser/parse/expr.rs similarity index 100% rename from src/parse/expr.rs rename to saltwater-parser/parse/expr.rs diff --git a/src/parse/mod.rs b/saltwater-parser/parse/mod.rs similarity index 100% rename from src/parse/mod.rs rename to saltwater-parser/parse/mod.rs diff --git a/src/parse/stmt.rs b/saltwater-parser/parse/stmt.rs similarity index 100% rename from src/parse/stmt.rs rename to saltwater-parser/parse/stmt.rs diff --git a/src/data/R2D2-Scream.ogg b/src/R2D2-Scream.ogg similarity index 100% rename from src/data/R2D2-Scream.ogg rename to src/R2D2-Scream.ogg diff --git a/src/arch/x64.rs b/src/arch/x64.rs deleted file mode 100644 index 77f83657..00000000 --- a/src/arch/x64.rs +++ /dev/null @@ -1,17 +0,0 @@ -// https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models -#[allow(non_camel_case_types)] -pub(crate) type SIZE_T = u64; -#[allow(dead_code)] -pub(crate) const SIZE_MAX: SIZE_T = SIZE_T::max_value(); - -pub(crate) const FLOAT_SIZE: u16 = 4; -pub(crate) const DOUBLE_SIZE: u16 = 8; - -pub(crate) const LONG_SIZE: u16 = 8; -pub(crate) const INT_SIZE: u16 = 4; -pub(crate) const SHORT_SIZE: u16 = 2; -pub(crate) const BOOL_SIZE: u16 = 1; - -pub(crate) const PTR_SIZE: u16 = 8; - -pub(crate) const CHAR_BIT: u16 = 8; // number of bits in a byte diff --git a/src/data/mod.rs b/src/data/mod.rs deleted file mode 100644 index 45538af3..00000000 --- a/src/data/mod.rs +++ /dev/null @@ -1,113 +0,0 @@ -pub mod ast; -pub mod error; -pub mod hir; -pub mod lex; -pub mod types; - -pub use crate::intern::InternedStr; -pub(crate) use error::ErrorHandler; -pub use error::{CompileError, CompileResult, CompileWarning, Error, SemanticError, SyntaxError}; -pub use hir::LiteralValue; -pub use lex::{LiteralToken, Locatable, Location, Token}; -pub use types::Type; -pub use types::{StructRef, StructType}; - -use std::convert::TryFrom; -use std::fmt::{self, Display}; - -use lex::Keyword; - -// used by both `ast` and `hir` -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum StorageClass { - Static, - Extern, - Auto, - Register, - Typedef, -} - -// helper functions for `Display` impls -fn joined, T: ToString>(it: I, delim: &str) -> String { - it.into_iter() - .map(|s| s.to_string()) - .collect::>() - .join(delim) -} - -fn joined_locatable<'a, I: IntoIterator>, T: ToString + 'a>( - it: I, - delim: &str, -) -> String { - joined(it.into_iter().map(|s| s.data.to_string()), delim) -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum Radix { - Binary, - Octal, - Decimal, - Hexadecimal, -} - -impl Radix { - pub fn as_u8(self) -> u8 { - match self { - Radix::Binary => 2, - Radix::Octal => 8, - Radix::Decimal => 10, - Radix::Hexadecimal => 16, - } - } -} - -impl Display for Radix { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let word = match self { - Radix::Binary => "binary", - Radix::Octal => "octal", - Radix::Decimal => "decimal", - Radix::Hexadecimal => "hexadecimal", - }; - write!(f, "{}", word) - } -} - -impl TryFrom for Radix { - type Error = (); - fn try_from(int: u32) -> Result { - match int { - 2 => Ok(Radix::Binary), - 8 => Ok(Radix::Octal), - 10 => Ok(Radix::Decimal), - 16 => Ok(Radix::Hexadecimal), - _ => Err(()), - } - } -} - -#[cfg(test)] -mod tests { - use crate::analyze::test::analyze; - use crate::Parser; - - #[test] - fn type_display() { - let types = [ - "int", - "int *", - "int [1][2][3]", - "char *(*)(float)", - "short *(*)[1][2][3]", - "_Bool", - "struct s", - ]; - for ty in types.iter() { - let parsed_ty = analyze(ty, Parser::type_name, |a, b| { - a.parse_typename_test(b.data, b.location) - }) - .unwrap(); - assert_eq!(&parsed_ty.to_string(), *ty); - } - } -} diff --git a/src/main.rs b/src/main.rs index e77f148a..2d0623d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,11 +9,9 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use ansi_term::{ANSIString, Colour}; use arcstr::ArcStr; use pico_args::Arguments; -use saltwater::{ - assemble, compile, - data::{error::CompileWarning, Location}, - link, preprocess, Error, Files, Opt, Program, -}; +use saltwater_codegen::{assemble, compile, link}; +use saltwater_parser::data::{error::CompileWarning, Location}; +use saltwater_parser::{preprocess, Error, Files, Opt, Program}; use tempfile::NamedTempFile; static ERRORS: AtomicUsize = AtomicUsize::new(0); @@ -163,7 +161,7 @@ fn real_main(buf: ArcStr, bin_opt: BinOpt, output: &Path) -> Result<(), (Error, #[inline] fn aot_main(buf: &str, opt: Opt, output: &Path, color: ColorChoice) -> Result<(), (Error, Files)> { let no_link = opt.no_link; - let module = saltwater::initialize_aot_module("saltwater_main".to_owned()); + let module = saltwater_codegen::initialize_aot_module("saltwater_main".to_owned()); let Program { result, warnings, @@ -305,7 +303,7 @@ fn parse_args() -> Result<(BinOpt, PathBuf), pico_args::Error> { std::process::exit(0); } if input.contains("--print-type-sizes") { - use saltwater::data::*; + use saltwater_parser::data::*; type_sizes!( Location, CompileError, @@ -343,7 +341,7 @@ fn parse_args() -> Result<(BinOpt, PathBuf), pico_args::Error> { let mut definitions = HashMap::new(); while let Some(arg) = input.opt_value_from_str::<_, String>(["-D", "--define"])? { use pico_args::Error::ArgumentParsingFailed; - use saltwater::data::error::LexError; + use saltwater_parser::data::error::LexError; use std::convert::TryInto; let mut iter = arg.splitn(2, '='); @@ -555,7 +553,7 @@ mod backtrace { #[cfg(feature = "salty")] fn play_scream() -> Result<(), ()> { - const SCREAM: &[u8] = include_bytes!("data/R2D2-Scream.ogg"); + const SCREAM: &[u8] = include_bytes!("R2D2-Scream.ogg"); let device = rodio::default_output_device().ok_or(())?; let source = rodio::Decoder::new(std::io::Cursor::new(SCREAM)).or(Err(()))?; rodio::play_raw(&device, rodio::source::Source::convert_samples(source)); @@ -605,7 +603,7 @@ fn install_panic_hook() { mod test { use super::{Files, Location}; use ansi_term::Style; - use saltwater::data::lex::Span; + use saltwater_parser::data::lex::Span; fn pp>(span: S, source: &str) -> String { let mut file_db = Files::new(); From 5cc8a4ba33cdd557b14b6b6c15940baf23b769aa Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Fri, 14 Aug 2020 23:33:05 -0400 Subject: [PATCH 2/7] Split up `saltwater_codegen` into a separate crate --- Cargo.toml | 4 ++-- saltwater-codegen/expr.rs | 4 ++-- saltwater-codegen/lib.rs | 8 ++++---- saltwater-codegen/static_init.rs | 9 +++------ saltwater-parser/Cargo.toml | 12 ++++-------- saltwater-parser/data/mod.rs | 17 ++++++++++++----- saltwater-parser/data/types.rs | 2 +- saltwater-parser/lex/cpp.rs | 4 ++-- saltwater-parser/macros.rs | 1 + tests/pre-commit.sh | 6 +++--- 10 files changed, 34 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cdd25923..597b7f03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,8 +49,6 @@ env_logger = { version = "0.7", default-features = false } log = "0.4" criterion = "0.3" walkdir = "2" -proptest = "^0.9.6" -proptest-derive = "0.1" [features] default = ["color-backtrace"] @@ -59,6 +57,8 @@ salty = ["rand", "rodio"] # for internal use _test_headers = [] +[workspace] + [[bin]] name = "swcc" path = "src/main.rs" diff --git a/saltwater-codegen/expr.rs b/saltwater-codegen/expr.rs index f90ebedc..6aa9f2d8 100644 --- a/saltwater-codegen/expr.rs +++ b/saltwater-codegen/expr.rs @@ -6,7 +6,7 @@ use super::{Compiler, Id}; use saltwater_parser::data::{ hir::{self, BinaryOp, Expr, ExprType, LiteralValue, Symbol, Variable}, lex::ComparisonToken, - * + *, }; type IrResult = CompileResult; @@ -504,8 +504,8 @@ impl Compiler { args: Vec, builder: &mut FunctionBuilder, ) -> IrResult { - use hir::Qualifiers; use cranelift::codegen::ir::{AbiParam, ArgumentPurpose}; + use hir::Qualifiers; let mut ftype = match ctype { Type::Function(ftype) => ftype, diff --git a/saltwater-codegen/lib.rs b/saltwater-codegen/lib.rs index 3fd798ca..be1a7aa9 100644 --- a/saltwater-codegen/lib.rs +++ b/saltwater-codegen/lib.rs @@ -18,8 +18,6 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::path::Path; -use saltwater_parser::{Program, Opt}; -use saltwater_parser::arch::TARGET; use cranelift::codegen::{ self, ir::{ @@ -35,6 +33,8 @@ use cranelift::frontend::Switch; use cranelift::prelude::{Block, FunctionBuilder, FunctionBuilderContext}; use cranelift_module::{self, Backend, DataId, FuncId, Linkage, Module}; use cranelift_object::{ObjectBackend, ObjectBuilder}; +use saltwater_parser::arch::TARGET; +use saltwater_parser::{Opt, Program}; use saltwater_parser::data::{ hir::{Declaration, Initializer, Stmt, Symbol}, @@ -357,7 +357,7 @@ pub type Product = ::Product; /// Compile and return the declarations and warnings. pub fn compile(module: Module, buf: &str, opt: Opt) -> Program> { - use saltwater_parser::{vec_deque, check_semantics}; + use saltwater_parser::{check_semantics, vec_deque}; let debug_asm = opt.debug_asm; let mut program = check_semantics(buf, opt); @@ -415,8 +415,8 @@ pub fn compile(module: Module, buf: &str, opt: Opt) -> Program Result<(), saltwater_parser::Error> { - use std::io::{self, Write}; use std::fs::File; + use std::io::{self, Write}; let bytes = product.emit().map_err(saltwater_parser::Error::Platform)?; File::create(output)? diff --git a/saltwater-codegen/static_init.rs b/saltwater-codegen/static_init.rs index a89223f8..0671a48f 100644 --- a/saltwater-codegen/static_init.rs +++ b/saltwater-codegen/static_init.rs @@ -8,13 +8,12 @@ use cranelift_module::{Backend, DataContext, DataId, Linkage}; use super::{Compiler, Id}; use saltwater_parser::arch::{PTR_SIZE, TARGET}; +use saltwater_parser::const_assert; use saltwater_parser::data::{ hir::{Expr, ExprType, Initializer, LiteralValue, Symbol}, types::ArrayType, - StorageClass, - * + StorageClass, *, }; -use saltwater_parser::const_assert; const_assert!(PTR_SIZE <= std::usize::MAX as u16); const ZERO_PTR: [u8; PTR_SIZE as usize] = [0; PTR_SIZE as usize]; @@ -432,9 +431,7 @@ fn linkage_from_storage_class(sc: StorageClass) -> Result { StorageClass::Extern => Ok(Linkage::Import), StorageClass::Static => Ok(Linkage::Local), StorageClass::Auto => Ok(Linkage::Export), - StorageClass::Register => { - Err(format!("illegal storage class {} for global variable", sc)) - } + StorageClass::Register => Err(format!("illegal storage class {} for global variable", sc)), StorageClass::Typedef => unreachable!("typedefs should be handled by parser"), } } diff --git a/saltwater-parser/Cargo.toml b/saltwater-parser/Cargo.toml index 99569912..30843067 100644 --- a/saltwater-parser/Cargo.toml +++ b/saltwater-parser/Cargo.toml @@ -11,24 +11,20 @@ path = "lib.rs" [dependencies] lazy_static = "1" arcstr = "0.2.2" -ansi_term = { version = "0.12", optional = true } hexponent = "0.3" thiserror = "^1.0.10" target-lexicon = "0.10" -tempfile = { version = "3", optional = true } -pico-args = { version = "0.3.4", optional = true, features = ["short-space-opt"] } lasso = "0.2" codespan = { version = ">=0.9.5", default-features = false } -color-backtrace = { version = "0.4", default-features = false, optional = true } counter = "0.4" -atty = { version = "0.2", default-features = false, optional = true } -git-testament = { version = "0.1", optional = true } -rand = { version = "0.7", optional = true } -rodio = { version = "0.11.0", optional = true } shared_str = "0.1.1" time = "0.2" cranelift-codegen = { version = "0.66", optional = true } cranelift-object = { version = "0.66", optional = true } +[dev-dependencies] +proptest = "^0.9.6" +proptest-derive = "0.1" + [features] codegen = ["cranelift-codegen", "cranelift-object"] diff --git a/saltwater-parser/data/mod.rs b/saltwater-parser/data/mod.rs index d59e513b..086c947d 100644 --- a/saltwater-parser/data/mod.rs +++ b/saltwater-parser/data/mod.rs @@ -5,7 +5,9 @@ pub mod lex; pub mod types; pub use crate::intern::InternedStr; -pub use error::{CompileError, CompileResult, CompileWarning, Error, ErrorHandler, SemanticError, SyntaxError}; +pub use error::{ + CompileError, CompileResult, CompileWarning, Error, ErrorHandler, SemanticError, SyntaxError, +}; pub use hir::LiteralValue; pub use lex::{LiteralToken, Locatable, Location, Token}; pub use types::Type; @@ -114,14 +116,19 @@ mod tests { #[cfg(feature = "codegen")] mod codegen_impls { use crate::arch::*; - use crate::data::{lex::{ComparisonToken, Locatable}, Type, types::FunctionType, error::CompileError}; + use crate::data::{ + error::CompileError, + lex::{ComparisonToken, Locatable}, + types::FunctionType, + Type, + }; use crate::intern::InternedStr; + use cranelift_codegen::ir::Signature; use cranelift_codegen::ir::{ - AbiParam, ArgumentPurpose, condcodes::{FloatCC, IntCC}, types::{self, Type as IrType}, + AbiParam, ArgumentPurpose, }; - use cranelift_codegen::ir::Signature; use cranelift_codegen::isa::{CallConv, TargetIsa}; // TODO: make this const when const_if_match is stabilized @@ -144,8 +151,8 @@ mod codegen_impls { } /// Return an IR type which can represent this C type pub fn as_ir_type(&self) -> IrType { - use Type::*; use std::convert::TryInto; + use Type::*; match self { // Integers diff --git a/saltwater-parser/data/types.rs b/saltwater-parser/data/types.rs index a844345d..b621e22e 100644 --- a/saltwater-parser/data/types.rs +++ b/saltwater-parser/data/types.rs @@ -57,7 +57,7 @@ mod struct_ref { /// /// Examples: /// ``` - /// use saltwater::data::types::StructRef; + /// use saltwater_parser::data::types::StructRef; /// let struct_ref = StructRef::new(); /// let members = struct_ref.get(); /// for symbol in members.iter() { diff --git a/saltwater-parser/lex/cpp.rs b/saltwater-parser/lex/cpp.rs index daf550cb..e0e16b7e 100644 --- a/saltwater-parser/lex/cpp.rs +++ b/saltwater-parser/lex/cpp.rs @@ -45,7 +45,7 @@ use crate::Files; /// /// Here is the example for `PreProcessor::new()` using the builder: /// ``` -/// use saltwater::PreProcessorBuilder; +/// use saltwater_parser::PreProcessorBuilder; /// /// let cpp = PreProcessorBuilder::new("int main(void) { char *hello = \"hi\"; }\n").filename("example.c").build(); /// for token in cpp { @@ -121,7 +121,7 @@ impl<'a> PreProcessorBuilder<'a> { /// Examples: /// /// ``` -/// use saltwater::PreProcessor; +/// use saltwater_parser::PreProcessor; /// /// let cpp = PreProcessor::new("int main(void) { char *hello = \"hi\"; }\n", "example.c", false, vec![], Default::default()); /// for token in cpp { diff --git a/saltwater-parser/macros.rs b/saltwater-parser/macros.rs index 7cccbdeb..ef779661 100644 --- a/saltwater-parser/macros.rs +++ b/saltwater-parser/macros.rs @@ -20,6 +20,7 @@ macro_rules! map { /// Very similar to `vec![]` from the standard library. /// Example: /// ```rust +/// use saltwater_parser::vec_deque; /// let queue = vec_deque![1, 2, 3]; /// ``` /// diff --git a/tests/pre-commit.sh b/tests/pre-commit.sh index c589f3e3..d31dd0fd 100755 --- a/tests/pre-commit.sh +++ b/tests/pre-commit.sh @@ -1,5 +1,5 @@ #!/bin/sh set -ev -cargo fmt -- --check -cargo clippy -- -D clippy::all -D unused-imports -cargo test --all-features +cargo fmt --all -- --check +cargo clippy --all -- -D clippy::all -D unused-imports +cargo test --all --all-features From 6fa882bfc9aa3f6a189bd1752c63b4041e5bfc81 Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Fri, 14 Aug 2020 23:57:36 -0400 Subject: [PATCH 3/7] Fix features --- Cargo.toml | 17 +---- saltwater-codegen/Cargo.toml | 2 +- saltwater-codegen/lib.rs | 124 +++++++++++++++++++++++++++++++++++ saltwater-parser/Cargo.toml | 1 + saltwater-parser/lib.rs | 8 --- src/main.rs | 4 +- tests/jit.rs | 3 +- tests/utils/mod.rs | 17 +++-- 8 files changed, 142 insertions(+), 34 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 597b7f03..ecac768b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,22 +21,11 @@ default-run = "swcc" documentation = "https://docs.rs/saltwater" [dependencies] -#lazy_static = "1" ansi_term = "0.12" arcstr = "0.2" -#cranelift = { version = "0.66", optional = true } -#cranelift-module = { version = "0.66", optional = true } -#cranelift-object = { version = "0.66", optional = true } -#cranelift-simplejit = { version = "0.66", optional = true } -#hexponent = "0.3" -#thiserror = "^1.0.10" -#target-lexicon = "0.10" tempfile = "3" pico-args = { version = "0.3.4", features = ["short-space-opt"] } -#lasso = "0.2" -#codespan = { version = ">=0.9.5", default-features = false } color-backtrace = { version = "0.4", default-features = false, optional = true } -#counter = "0.4" atty = { version = "0.2", default-features = false } git-testament = { version = "0.1", optional = true } rand = { version = "0.7", optional = true } @@ -52,8 +41,8 @@ walkdir = "2" [features] default = ["color-backtrace"] -#codegen = ["cranelift", "cranelift-module", "cranelift-object"] salty = ["rand", "rodio"] +jit = ["saltwater-codegen/jit"] # for internal use _test_headers = [] @@ -78,16 +67,14 @@ required-features = ["jit"] [[test]] name = "runner" -required-features = ["cc"] [[test]] name = "varargs" -required-features = ["cc"] [[test]] name = "headers" # MacOS breaks if you pass -undef to the system preprocessor -required-features = ["cc", "_test_header"] +required-features = ["_test_header"] [profile.release] lto = true diff --git a/saltwater-codegen/Cargo.toml b/saltwater-codegen/Cargo.toml index 2d55855b..f3edd05d 100644 --- a/saltwater-codegen/Cargo.toml +++ b/saltwater-codegen/Cargo.toml @@ -20,4 +20,4 @@ lazy_static = "1" saltwater-parser = { path = "../saltwater-parser", features = ["codegen"] } [features] -jit = ["cranelift-simplejit"] +jit = ["cranelift-simplejit", "saltwater-parser/jit"] diff --git a/saltwater-codegen/lib.rs b/saltwater-codegen/lib.rs index be1a7aa9..7be83020 100644 --- a/saltwater-codegen/lib.rs +++ b/saltwater-codegen/lib.rs @@ -449,6 +449,130 @@ pub fn link(obj_file: &Path, output: &Path) -> Result<(), std::io::Error> { } } +#[cfg(feature = "jit")] +pub use jit::*; + +#[cfg(feature = "jit")] +mod jit { + use super::*; + use cranelift_simplejit::{SimpleJITBackend, SimpleJITBuilder}; + use std::convert::TryFrom; + use std::rc::Rc; + + pub fn initialize_jit_module() -> Module { + let libcall_names = cranelift_module::default_libcall_names(); + Module::new(SimpleJITBuilder::with_isa(get_isa(true), libcall_names)) + } + + /// Structure used to handle compiling C code to memory instead of to disk. + /// + /// You can use [`from_string`] to create a JIT instance. + /// Alternatively, if you don't care about compile warnings, you can use `JIT::try_from` instead. + /// If you already have a `Module`, you can use `JIT::from` to avoid having to `unwrap()`. + /// + /// JIT stands for 'Just In Time' compiled, the way that Java and JavaScript work. + /// + /// [`from_string`]: #method.from_string + pub struct JIT { + module: Module, + } + + impl From> for JIT { + fn from(module: Module) -> Self { + Self { module } + } + } + + impl TryFrom> for JIT { + type Error = saltwater_parser::Error; + fn try_from(source: Rc) -> Result { + JIT::from_string(source, Opt::default()).result + } + } + + impl JIT { + /// Compile string and return JITed code. + pub fn from_string>>( + source: R, + opt: Opt, + ) -> Program { + let source = source.into(); + let module = initialize_jit_module(); + let program = compile(module, &source, opt); + let result = match program.result { + Ok(module) => Ok(JIT::from(module)), + Err(errs) => Err(errs.into()), + }; + Program { + result, + warnings: program.warnings, + files: program.files, + } + } + + /// Invoke this function before trying to get access to "new" compiled functions. + pub fn finalize(&mut self) { + self.module.finalize_definitions(); + } + /// Get a compiled function. If this function doesn't exist then `None` is returned, otherwise its address returned. + /// + /// # Panics + /// Panics if function is not compiled (finalized). Try to invoke `finalize` before using `get_compiled_function`. + pub fn get_compiled_function(&mut self, name: &str) -> Option<*const u8> { + use cranelift_module::FuncOrDataId; + + let name = self.module.get_name(name); + if let Some(FuncOrDataId::Func(id)) = name { + Some(self.module.get_finalized_function(id)) + } else { + None + } + } + /// Get compiled static data. If this data doesn't exist then `None` is returned, otherwise its address and size are returned. + pub fn get_compiled_data(&mut self, name: &str) -> Option<(*mut u8, usize)> { + use cranelift_module::FuncOrDataId; + + let name = self.module.get_name(name); + if let Some(FuncOrDataId::Data(id)) = name { + Some(self.module.get_finalized_data(id)) + } else { + None + } + } + /// Given a module, run the `main` function. + /// + /// This automatically calls `self.finalize()`. + /// If `main()` does not exist in the module, returns `None`; otherwise returns the exit code. + /// + /// # Safety + /// This function runs arbitrary C code. + /// It can segfault, access out-of-bounds memory, cause data races, or do anything else C can do. + #[allow(unsafe_code)] + pub unsafe fn run_main(&mut self) -> Option { + self.finalize(); + let main = self.get_compiled_function("main")?; + let args = std::env::args().skip(1); + let argc = args.len() as i32; + // CString should be alive if we want to pass its pointer to another function, + // otherwise this may lead to UB. + let vec_args = args + .map(|string| std::ffi::CString::new(string).unwrap()) + .collect::>(); + // This vec needs to be stored so we aren't passing a pointer to a freed temporary. + let argv = vec_args + .iter() + .map(|cstr| cstr.as_ptr() as *const u8) + .collect::>(); + assert_ne!(main, std::ptr::null()); + // this transmute is safe: this function is finalized (`self.finalize()`) + // and **guaranteed** to be non-null + let main: unsafe extern "C" fn(i32, *const *const u8) -> i32 = + std::mem::transmute(main); + // though transmute is safe, invoking this function is unsafe because we invoke C code. + Some(main(argc, argv.as_ptr() as *const *const u8)) + } + } +} #[cfg(test)] #[test] fn test_compile_error_semantic() { diff --git a/saltwater-parser/Cargo.toml b/saltwater-parser/Cargo.toml index 30843067..7014e864 100644 --- a/saltwater-parser/Cargo.toml +++ b/saltwater-parser/Cargo.toml @@ -28,3 +28,4 @@ proptest-derive = "0.1" [features] codegen = ["cranelift-codegen", "cranelift-object"] +jit = [] diff --git a/saltwater-parser/lib.rs b/saltwater-parser/lib.rs index c30ec07d..1034fbb0 100644 --- a/saltwater-parser/lib.rs +++ b/saltwater-parser/lib.rs @@ -12,14 +12,6 @@ use std::path::PathBuf; use std::rc::Rc; pub use codespan; - -#[cfg(all(feature = "color-backtrace", not(feature = "cc")))] -compile_error!(concat!( - "The color-backtrace feature does nothing unless used by the `", - env!("CARGO_PKG_DIR"), - "` binary." -)); - use arcstr::ArcStr; /// The `Source` type for `codespan::Files`. diff --git a/src/main.rs b/src/main.rs index 2d0623d5..d5ec1253 100644 --- a/src/main.rs +++ b/src/main.rs @@ -140,14 +140,14 @@ fn real_main(buf: ArcStr, bin_opt: BinOpt, output: &Path) -> Result<(), (Error, if !opt.jit { aot_main(&buf, opt, output, bin_opt.color) } else { - let module = saltwater::initialize_jit_module(); + let module = saltwater_codegen::initialize_jit_module(); let Program { result, warnings, files, } = compile(module, &buf, opt); handle_warnings(warnings, &files, bin_opt.color); - let mut jit = saltwater::JIT::from(sw_try!(result, files)); + let mut jit = saltwater_codegen::JIT::from(sw_try!(result, files)); if let Some(exit_code) = unsafe { jit.run_main() } { std::process::exit(exit_code); } diff --git a/tests/jit.rs b/tests/jit.rs index bb4226be..adbf83a1 100644 --- a/tests/jit.rs +++ b/tests/jit.rs @@ -1,6 +1,7 @@ mod utils; -use saltwater::{Opt, Program, JIT}; +use saltwater_codegen::JIT; +use saltwater_parser::{Opt, Program}; #[test] fn jit_readme() -> Result<(), Box> { diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 66f8871b..6c601b0c 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -8,7 +8,8 @@ extern crate log; extern crate tempfile; use log::info; -use saltwater::Error; +use saltwater_codegen::{assemble, initialize_aot_module, link}; +use saltwater_parser::{Error, Opt}; pub fn init() { env_logger::builder().is_test(true).init(); @@ -45,12 +46,14 @@ pub fn compile( filename: PathBuf, no_link: bool, ) -> Result { - let opts = saltwater::Opt { + let opts = Opt { filename, ..Default::default() }; - let module = saltwater::initialize_aot_module(program.to_owned()); - let module = saltwater::compile(module, program, opts).result?.finish(); + let module = initialize_aot_module(program.to_owned()); + let module = saltwater_codegen::compile(module, program, opts) + .result? + .finish(); let output = tempfile::NamedTempFile::new() .expect("cannot create tempfile") .into_temp_path(); @@ -60,10 +63,10 @@ pub fn compile( .expect("cannot create tempfile") .into_temp_path(); info!("tmp_file is {:?}", tmp_file); - saltwater::assemble(module, &tmp_file)?; - saltwater::link(&tmp_file, &output)?; + assemble(module, &tmp_file)?; + link(&tmp_file, &output)?; } else { - saltwater::assemble(module, &output)?; + assemble(module, &output)?; }; Ok(output) } From 33d8be6ec8ded6931758bdc01b68024adf01d135 Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Sat, 15 Aug 2020 00:33:38 -0400 Subject: [PATCH 4/7] Allow publishing again --- Cargo.toml | 4 ++-- saltwater-codegen/Cargo.toml | 2 +- saltwater-parser/Cargo.toml | 2 +- {headers => saltwater-parser/headers}/stdarg.h | 0 {headers => saltwater-parser/headers}/stddef.h | 0 saltwater-parser/lex/cpp.rs | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename {headers => saltwater-parser/headers}/stdarg.h (100%) rename {headers => saltwater-parser/headers}/stddef.h (100%) diff --git a/Cargo.toml b/Cargo.toml index ecac768b..7943adc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,8 @@ atty = { version = "0.2", default-features = false } git-testament = { version = "0.1", optional = true } rand = { version = "0.7", optional = true } rodio = { version = "0.11.0", optional = true } -saltwater-parser = { path = "saltwater-parser" } -saltwater-codegen = { path = "saltwater-codegen" } +saltwater-parser = { path = "saltwater-parser", version = "0.11.0" } +saltwater-codegen = { path = "saltwater-codegen", version = "0.11.0" } [dev-dependencies] env_logger = { version = "0.7", default-features = false } diff --git a/saltwater-codegen/Cargo.toml b/saltwater-codegen/Cargo.toml index f3edd05d..f13f1e1d 100644 --- a/saltwater-codegen/Cargo.toml +++ b/saltwater-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "saltwater-codegen" -version = "0.1.0" +version = "0.11.0" authors = ["Joshua Nelson "] edition = "2018" diff --git a/saltwater-parser/Cargo.toml b/saltwater-parser/Cargo.toml index 7014e864..439c23de 100644 --- a/saltwater-parser/Cargo.toml +++ b/saltwater-parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "saltwater-parser" -version = "0.1.0" +version = "0.11.0" authors = ["Joshua Nelson "] edition = "2018" diff --git a/headers/stdarg.h b/saltwater-parser/headers/stdarg.h similarity index 100% rename from headers/stdarg.h rename to saltwater-parser/headers/stdarg.h diff --git a/headers/stddef.h b/saltwater-parser/headers/stddef.h similarity index 100% rename from headers/stddef.h rename to saltwater-parser/headers/stddef.h diff --git a/saltwater-parser/lex/cpp.rs b/saltwater-parser/lex/cpp.rs index e0e16b7e..6ad34aac 100644 --- a/saltwater-parser/lex/cpp.rs +++ b/saltwater-parser/lex/cpp.rs @@ -1179,7 +1179,7 @@ macro_rules! built_in_headers { ( $($filename: literal),+ $(,)? ) => { [ // Relative to the current file, not the crate root - $( ($filename, include_str!(concat!("../../headers/", $filename))) ),+ + $( ($filename, include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/headers/", $filename))) ),+ ] }; } From c8c76060563d8afea3d3a395a384f9f63e9a65e9 Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Sat, 15 Aug 2020 15:16:15 -0400 Subject: [PATCH 5/7] Remove commented-out code --- saltwater-codegen/lib.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/saltwater-codegen/lib.rs b/saltwater-codegen/lib.rs index 7be83020..a42cb320 100644 --- a/saltwater-codegen/lib.rs +++ b/saltwater-codegen/lib.rs @@ -96,16 +96,6 @@ struct Compiler { error_handler: ErrorHandler, } -/* -/// Compile a program from a high level IR to a Cranelift Module -pub(crate) fn compile( - module: Module, - program: Vec>, - debug: bool, -) -> (Result, CompileError>, VecDeque) { -} -*/ - impl Compiler { fn new(module: Module, debug: bool) -> Compiler { Compiler { From fcc7145fb2f3ad37e62a332bdd1ce164eeb2c80e Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Sat, 15 Aug 2020 15:20:57 -0400 Subject: [PATCH 6/7] Add back whitespace --- saltwater-codegen/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/saltwater-codegen/lib.rs b/saltwater-codegen/lib.rs index a42cb320..0785ab6e 100644 --- a/saltwater-codegen/lib.rs +++ b/saltwater-codegen/lib.rs @@ -563,6 +563,7 @@ mod jit { } } } + #[cfg(test)] #[test] fn test_compile_error_semantic() { From 32e82272397b6d75b7c0415bc0df4c684f4a0ad7 Mon Sep 17 00:00:00 2001 From: Joshua Nelson Date: Sun, 6 Sep 2020 12:20:13 -0400 Subject: [PATCH 7/7] cargo fmt --- saltwater-parser/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/saltwater-parser/lib.rs b/saltwater-parser/lib.rs index 1034fbb0..893f9f4d 100644 --- a/saltwater-parser/lib.rs +++ b/saltwater-parser/lib.rs @@ -11,8 +11,8 @@ use std::io; use std::path::PathBuf; use std::rc::Rc; -pub use codespan; use arcstr::ArcStr; +pub use codespan; /// The `Source` type for `codespan::Files`. ///