Skip to content

Commit

Permalink
Initial String support (#194)
Browse files Browse the repository at this point in the history
* Initial string support

* better error

* split coverage
  • Loading branch information
edg-l authored Feb 26, 2025
1 parent 4a3f89a commit a113f03
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 12 deletions.
28 changes: 23 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,30 @@ jobs:
uses: taiki-e/install-action@cargo-llvm-cov
- name: test and generate coverage
run: make coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
- name: save coverage data
uses: actions/upload-artifact@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: lcov.info
fail_ci_if_error: true
name: coverage-data
path: ./lcov.info
upload-coverage:
name: Upload Coverage
runs-on: ubuntu-latest
needs: [coverage]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download coverage artifacts
uses: actions/download-artifact@v4
with:
name: coverage-data
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./lcov.info
fail_ci_if_error: true
verbose: true

bench:
name: Bench
runs-on: ubuntu-latest
Expand Down
18 changes: 18 additions & 0 deletions examples/hello_world.con
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
mod hello {
extern fn malloc(size: u64) -> *mut u8;
extern fn puts(ptr: *mut u8) -> i32;

#[langitem = "String"]
struct String {
ptr: *mut u8,
len: u64,
cap: u64,
}

fn main() -> u64 {
let x: String = "hello \nworld!";
puts(x.ptr);

return 0;
}
}
3 changes: 2 additions & 1 deletion src/ast/structs.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use super::{
common::{GenericParam, Ident, Span},
common::{Attribute, GenericParam, Ident, Span},
types::TypeDescriptor,
};

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct StructDecl {
pub name: Ident,
pub generics: Vec<GenericParam>,
pub attributes: Vec<Attribute>,
pub fields: Vec<Field>,
pub is_pub: bool,
pub span: Span,
Expand Down
12 changes: 12 additions & 0 deletions src/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,5 +306,17 @@ pub fn lowering_error_to_report(error: LoweringError) -> Report<'static, FileSpa
}
report.finish()
}
LoweringError::UnknownLangItem { span, item, path } => {
let path = path.display().to_string();
let filespan = FileSpan::new(path.clone(), span.from..span.to);
let report = Report::build(ReportKind::Error, filespan.clone())
.with_code("UnknownLangItem")
.with_label(
Label::new(filespan.clone())
.with_message(format!("unknown lang item '{}'", item))
.with_color(colors.next()),
);
report.finish()
}
}
}
65 changes: 63 additions & 2 deletions src/codegen/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::ir::{
ModuleIndex, Operand, Place, PlaceElem, Rvalue, Span, Type as IRType, TypeIndex, ValueTree,
};
use ariadne::Source;
use melior::helpers::ArithBlockExt;
use melior::helpers::{ArithBlockExt, BuiltinBlockExt, GepIndex, LlvmBlockExt};
use melior::ir::{BlockLike, RegionLike};
use melior::{
Context as MeliorContext,
Expand Down Expand Up @@ -1310,6 +1310,7 @@ fn value_tree_to_int(value: &ValueTree) -> Option<i64> {
crate::ir::ConstValue::U128(value) => Some((*value) as i64),
crate::ir::ConstValue::F32(_) => None,
crate::ir::ConstValue::F64(_) => None,
crate::ir::ConstValue::String(_) => None,
},
ValueTree::Branch(_) => None,
}
Expand All @@ -1323,6 +1324,57 @@ fn compile_value_tree<'c: 'b, 'b>(
) -> Result<Value<'c, 'b>, CodegenError> {
Ok(match value {
ValueTree::Leaf(value) => match value {
crate::ir::ConstValue::String(data) => {
let location = Location::unknown(ctx.context());
let ty = compile_type(ctx.module, &IRType::String);
let len = data.len();
let u64_ty = IntegerType::new(ctx.context(), 64).into();
let u8_ty = IntegerType::new(ctx.context(), 8).into();
// + 1 for the null byte.
let len_value =
block.const_int_from_type(ctx.context(), location, len + 1, u64_ty)?;

let arr_ty = llvm::r#type::array(u8_ty, len as u32);

let constant = block.append_op_result(
ods::llvm::mlir_constant(
ctx.context(),
arr_ty,
StringAttribute::new(ctx.context(), data).into(),
location,
)
.into(),
)?;

let ptr = block.append_op_result(func::call(
ctx.context(),
FlatSymbolRefAttribute::new(ctx.context(), "malloc"),
&[len_value],
&[llvm::r#type::pointer(ctx.context(), 0)],
location,
))?;

block.store(ctx.context(), location, ptr, constant)?;
let ptr_off = block.gep(
ctx.context(),
location,
ptr,
&[GepIndex::Const(len as i32)],
u8_ty,
)?;
// add the null byte.
let const_0 = block.const_int(ctx.context(), location, 0, 64)?;
block.store(ctx.context(), location, ptr_off, const_0)?;

let struct_value = block.append_op_result(llvm::undef(ty, location))?;

block.insert_values(
ctx.context(),
location,
struct_value,
&[ptr, len_value, len_value],
)?
}
crate::ir::ConstValue::Bool(value) => block
.append_operation(arith::constant(
ctx.context(),
Expand Down Expand Up @@ -1473,7 +1525,16 @@ fn compile_type<'c>(ctx: ModuleCodegenCtx<'c>, ty: &IRType) -> Type<'c> {
crate::ir::FloatTy::F32 => Type::float32(ctx.ctx.mlir_context),
crate::ir::FloatTy::F64 => Type::float64(ctx.ctx.mlir_context),
},
crate::ir::Type::String => todo!(),
crate::ir::Type::String => {
let ty = *ctx
.ctx
.program
.builtin_types
.get(&crate::ir::Type::String)
.unwrap();
let ty = ctx.get_type(ty);
compile_type(ctx, &ty)
}
crate::ir::Type::Array(inner_type_idx, length) => {
let inner_type = ctx.get_type(*inner_type_idx);
let inner_type = compile_type(ctx, &inner_type);
Expand Down
4 changes: 3 additions & 1 deletion src/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -401,10 +401,12 @@ StructField: ast::structs::Field = {
}
}


StructDef: ast::structs::StructDecl = {
<lo:@L> <is_pub:"pub"?> "struct" <name:Ident> <generics:GenericParams?> "{" <fields:Comma<StructField>> "}" <hi:@R> => ast::structs::StructDecl {
<lo:@L> <attributes:List<Attribute>?> <is_pub:"pub"?> "struct" <name:Ident> <generics:GenericParams?> "{" <fields:Comma<StructField>> "}" <hi:@R> => ast::structs::StructDecl {
name,
fields,
attributes: attributes.unwrap_or_default(),
is_pub: is_pub.is_some(),
generics: generics.unwrap_or(vec![]),
span: Span::new(lo, hi),
Expand Down
6 changes: 6 additions & 0 deletions src/ir/lowering/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,10 @@ pub enum LoweringError {
needs: usize,
path: PathBuf,
},
#[error("unknown lang item {item}")]
UnknownLangItem {
span: Span,
item: String,
path: PathBuf,
},
}
10 changes: 9 additions & 1 deletion src/ir/lowering/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,15 @@ pub(crate) fn lower_value_expr(

(Rvalue::Use(Operand::Const(data)), ty)
}
ValueExpr::ConstStr(_, _) => todo!(),
ValueExpr::ConstStr(value, span) => {
let ty = fn_builder.builder.ir.get_string_ty();
let data = ConstData {
ty,
span: *span,
data: ConstKind::Value(ValueTree::Leaf(ConstValue::String(value.clone()))),
};
(Rvalue::Use(Operand::Const(data)), ty)
}
ValueExpr::Path(info) => {
if fn_builder.name_to_local.contains_key(&info.first.name) {
let (place, place_ty, _span) = lower_path(fn_builder, info)?;
Expand Down
20 changes: 20 additions & 0 deletions src/ir/lowering/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,26 @@ fn lower_module_symbols(
builder.ir.modules[module_idx].types.insert(type_idx);
builder.struct_to_type_idx.insert(idx, type_idx);
builder.type_to_module.insert(type_idx, module_idx);

for attr in &struct_decl.attributes {
if attr.name == "langitem" {
let langitem = attr.value.as_ref().unwrap();

match langitem.as_str() {
"String" => {
builder.ir.builtin_types.insert(Type::String, type_idx);
}
_ => {
return Err(LoweringError::UnknownLangItem {
span: attr.span,
item: attr.name.clone(),
path: builder.get_current_module().file_path.clone(),
});
}
}
}
}

debug!(
"Adding struct symbol {:?} to module {:?}",
struct_decl.name.name, module.name.name
Expand Down
2 changes: 1 addition & 1 deletion src/ir/lowering/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub(crate) fn lower_type(
.get(&Type::Float(FloatTy::F64))
.unwrap(),
"bool" => *builder.ir.builtin_types.get(&Type::Bool).unwrap(),
"string" => *builder.ir.builtin_types.get(&Type::String).unwrap(),
"String" => *builder.ir.builtin_types.get(&Type::String).unwrap(),
"char" => *builder.ir.builtin_types.get(&Type::Char).unwrap(),
other => {
// Check if the type name exists in the generic map.
Expand Down
1 change: 1 addition & 0 deletions src/ir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,7 @@ pub enum ConstValue {
U128(u128),
F32(String),
F64(String),
String(String),
}

#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
Expand Down
2 changes: 1 addition & 1 deletion tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,5 +129,5 @@ pub fn compile_and_run_output(

let output = run_program(&result.binary_file).expect("failed to run");

std::str::from_utf8(&output.stdout).unwrap().to_string()
String::from_utf8_lossy(&output.stdout).to_string()
}
1 change: 1 addition & 0 deletions tests/examples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ fn example_tests(source: &str, name: &str, is_library: bool, status_code: i32) {

#[test_case(include_str!("../examples/hello_world_hacky.con"), "hello_world_hacky", false, "Hello World\n" ; "hello_world_hacky.con")]
#[test_case(include_str!("../examples/hello_world_array.con"), "hello_world_array", false, "hello world!\n" ; "hello_world_array.con")]
#[test_case(include_str!("../examples/hello_world.con"), "hello_world", false, "hello \nworld!\n" ; "hello_world.con")]
fn example_tests_with_output(source: &str, name: &str, is_library: bool, result: &str) {
assert_eq!(
result,
Expand Down

0 comments on commit a113f03

Please sign in to comment.