Skip to content

Commit

Permalink
Merge pull request #992 from hash-org/impl-repeat-expr
Browse files Browse the repository at this point in the history
implement repeat expressions
  • Loading branch information
feds01 authored Sep 25, 2023
2 parents f9c8e9c + 8d220ed commit f64c50a
Show file tree
Hide file tree
Showing 43 changed files with 721 additions and 234 deletions.
15 changes: 15 additions & 0 deletions compiler/hash-ast-utils/src/pretty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,21 @@ where
self.write("]")
}

type RepeatExprRet = ();

fn visit_repeat_expr(
&mut self,
node: ast::AstNodeRef<ast::RepeatExpr>,
) -> Result<Self::RepeatExprRet, Self::Error> {
let ast::RepeatExpr { subject, repeat } = node.body();

self.write("[")?;
self.visit_expr(subject.ast_ref())?;
self.write("; ")?;
self.visit_expr(repeat.ast_ref())?;
self.write("]")
}

type TupleLitEntryRet = ();

fn visit_tuple_lit_entry(
Expand Down
17 changes: 17 additions & 0 deletions compiler/hash-ast-utils/src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,23 @@ impl AstVisitor for AstTreePrinter {
))
}

type RepeatExprRet = TreeNode;

fn visit_repeat_expr(
&self,
node: ast::AstNodeRef<ast::RepeatExpr>,
) -> Result<Self::RepeatExprRet, Self::Error> {
let walk::RepeatExpr { subject, repeat } = walk::walk_repeat_expr(self, node)?;

Ok(TreeNode::branch(
"repeat_expr",
vec![
TreeNode::branch("subject", vec![subject]),
TreeNode::branch("repeat", vec![repeat]),
],
))
}

type StructDefRet = TreeNode;
fn visit_struct_def(
&self,
Expand Down
20 changes: 20 additions & 0 deletions compiler/hash-ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,11 @@ impl<T> AstNodes<T> {
SpanMap::span_of(self.id)
}

/// Insert an item into the [AstNodes] at a particular index.
pub fn insert(&mut self, item: AstNode<T>, index: usize) {
self.nodes.insert(index, item);
}

/// Merge two [AstNodes] together, this will append the nodes of the
/// other [AstNodes] to this one, and then return the new [AstNodes].
///
Expand Down Expand Up @@ -2235,6 +2240,17 @@ define_tree! {
pub index_expr: Child!(Expr),
}
/// A repeat expression `[x; 5]`.
#[derive(Debug, PartialEq, Clone)]
#[node]
pub struct RepeatExpr {
/// The subject that is being repeated.
pub subject: Child!(Expr),
/// The constant specifying the number of repeats of the subject.
pub repeat: Child!(Expr),
}
/// An expression.
#[derive(Debug, PartialEq, Clone)]
#[node]
Expand Down Expand Up @@ -2315,6 +2331,10 @@ define_tree! {
/// Expression to index a subject e.g. `arr[x]`
Index(IndexExpr),

/// An expression that specifies that an operand is to be
/// repeated a certain number of times, e.g. `[1; 5]`.
Repeat(RepeatExpr),

/// An expression that captures a variable or a pattern being assigned
/// to a right hand-side expression such as `x = 3`.
Assign(AssignExpr),
Expand Down
92 changes: 55 additions & 37 deletions compiler/hash-codegen-llvm/src/translation/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use llvm_sys::core as llvm;

use super::{
abi::ExtendedFnAbiMethods, layouts::ExtendedLayoutMethods, ty::ExtendedTyBuilderMethods,
LLVMBuilder,
LLVMBuilder, EMPTY_NAME,
};
use crate::misc::{
AtomicOrderingWrapper, FloatPredicateWrapper, IntPredicateWrapper, MetadataTypeKind,
Expand Down Expand Up @@ -71,7 +71,7 @@ impl<'a, 'b, 'm> LLVMBuilder<'a, 'b, 'm> {

// Create the PHI value, and then add all of the incoming values.
let ty: BasicTypeEnum = ty.try_into().unwrap();
let phi = self.builder.build_phi(ty, "phi");
let phi = self.builder.build_phi(ty, "");

// @@Efficiency: patch inkwell to allow to provide these references directly...
let blocks_and_values = blocks
Expand Down Expand Up @@ -464,10 +464,21 @@ impl<'a, 'b, 'm> BlockBuilderMethods<'a, 'b> for LLVMBuilder<'a, 'b, 'm> {
}

fn unchecked_uadd(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value {
let lhs = lhs.into_int_value();
let rhs = rhs.into_int_value();

self.builder.build_int_nuw_add(lhs, rhs, "").into()
// @@PatchInkwell: we need to potentially support `phi_value` from
// the lhs, so we can't use the standard builder method here...
//
// let lhs = lhs.into_int_value();
// let rhs = rhs.into_int_value();
// self.builder.build_int_nuw_add(lhs, rhs, "").into()
//
unsafe {
AnyValueEnum::new(llvm::LLVMBuildNUWAdd(
self.builder.as_mut_ptr(),
lhs.as_value_ref(),
rhs.as_value_ref(),
EMPTY_NAME,
))
}
}

fn unchecked_ssub(&mut self, lhs: Self::Value, rhs: Self::Value) -> Self::Value {
Expand Down Expand Up @@ -579,10 +590,20 @@ impl<'a, 'b, 'm> BlockBuilderMethods<'a, 'b> for LLVMBuilder<'a, 'b, 'm> {

fn icmp(&mut self, op: IntComparisonKind, lhs: Self::Value, rhs: Self::Value) -> Self::Value {
let op = IntPredicateWrapper::from(op).0;
let lhs = lhs.into_int_value();
let rhs = rhs.into_int_value();

self.builder.build_int_compare(op, lhs, rhs, "").into()
// @@PatchInkwell: `built_int_compare` doesn't support passing in `phi` values
// because of the `IntMathType` bound on the operands.
unsafe {
let value = llvm::LLVMBuildICmp(
self.builder.as_mut_ptr(),
op.into(),
lhs.as_value_ref(),
rhs.as_value_ref(),
EMPTY_NAME,
);

AnyValueEnum::new(value)
}
}

fn fcmp(&mut self, op: RealComparisonKind, lhs: Self::Value, rhs: Self::Value) -> Self::Value {
Expand Down Expand Up @@ -751,8 +772,6 @@ impl<'a, 'b, 'm> BlockBuilderMethods<'a, 'b> for LLVMBuilder<'a, 'b, 'm> {
) {
let zero = self.const_usize(0);
let count = self.const_usize(count as u64);
let start = destination.project_index(self, zero).value;
let end = destination.project_index(self, count).value;

// setup the blocks to write the operand to...
let header_bb = self.append_sibling_block("repeat_loop_header");
Expand All @@ -764,37 +783,20 @@ impl<'a, 'b, 'm> BlockBuilderMethods<'a, 'b> for LLVMBuilder<'a, 'b, 'm> {

let mut header_builder = Self::build(self.ctx, header_bb);

let basic_value: BasicValueEnum = start.try_into().unwrap();
let zero_val: BasicValueEnum = zero.try_into().unwrap();
let current: AnyValueEnum = header_builder
.phi(
self.ctx.ty_of_value(start),
&[&basic_value as &dyn BasicValue],
&[self.basic_block()],
)
.phi(self.ctx.ty_of_value(zero), &[&zero_val], &[self.basic_block()])
.into();

let keep_going = header_builder.icmp(IntComparisonKind::Ne, current, end);
let keep_going = header_builder.icmp(IntComparisonKind::Ult, current, count);
header_builder.conditional_branch(keep_going, body_bb, next_bb);

let mut body_builder = Self::build(self.ctx, body_bb);
let dest = destination.project_index(&mut body_builder, current);
operand.value.store(&mut body_builder, dest);

let field_info = destination.info.field(self.ctx.layouts(), 0);
let field_size = field_info.size();

let alignment = destination.alignment.restrict_to(field_size);

// now we want to emit a `store` for the value we are writing
operand.value.store(
&mut body_builder,
PlaceRef { value: current, extra: None, info: operand.info, alignment },
);

// Compute the "next" value...
let ty = self.backend_ty_from_info(operand.info);
let next: BasicValueEnum = body_builder
.bounded_get_element_pointer(ty, current, &[self.const_usize(1)])
.try_into()
.unwrap();
let next: BasicValueEnum =
body_builder.unchecked_uadd(current, self.const_usize(1)).try_into().unwrap();

// and branch back to the header
body_builder.branch(header_bb);
Expand Down Expand Up @@ -1013,13 +1015,29 @@ impl<'a, 'b, 'm> BlockBuilderMethods<'a, 'b> for LLVMBuilder<'a, 'b, 'm> {
indices: &[Self::Value],
) -> Self::Value {
let ty: BasicTypeEnum = ty.try_into().unwrap();
let indices = indices.iter().map(|i| i.into_int_value()).collect::<Vec<_>>();

// ## Safety: If the `indices` are invalid or out of bounds, LLVM
// is likely to segfault, which is noted by Inkwell and thus labelled
// as `unsafe`.
unsafe {
self.builder.build_in_bounds_gep(ty, ptr.into_pointer_value(), &indices, "").into()
let pointee = ptr.into_pointer_value();
let mut index_values: Vec<llvm_sys::prelude::LLVMValueRef> =
indices.iter().map(|val| val.as_value_ref()).collect();

// @@PatchInkwell, we can't use `self.builder.build_in_bounds_gep()` because
// again of the possibility of the `indices` to be non-int values.
//
// We need to patch inkwell to support this.
let value = llvm::LLVMBuildInBoundsGEP2(
self.builder.as_mut_ptr(),
ty.as_type_ref(),
pointee.as_value_ref(),
index_values.as_mut_ptr(),
index_values.len() as u32,
EMPTY_NAME,
);

AnyValueEnum::new(value)
}
}

Expand Down
9 changes: 9 additions & 0 deletions compiler/hash-codegen-llvm/src/translation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
//! logic of transforming generated Hash IR into LLVM IR so that it can be
//! compiled by LLVM into a native executable with the specified target triple.
use std::ffi::{c_char, CStr};

use hash_codegen::{
backend::CodeGenStorage,
layout::compute::LayoutComputer,
Expand All @@ -25,6 +27,13 @@ mod misc;
mod statics;
pub(crate) mod ty;

/// An empty c-string.
const EMPTY_C_STR: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };

/// Used to fill in names where LLVM requires a name for an instruction, or some
/// value. We don't care about the names, so we just use an empty c-string.
pub(crate) const EMPTY_NAME: *const c_char = EMPTY_C_STR.as_ptr();

/// A [Builder] is defined as being a context that is used to implement
/// all of the specified builder methods.
pub struct LLVMBuilder<'a, 'b, 'm> {
Expand Down
41 changes: 38 additions & 3 deletions compiler/hash-codegen/src/lower/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use super::{
FnBuilder,
};
use crate::{
common::{CheckedOp, IntComparisonKind, TypeKind},
common::{CheckedOp, IntComparisonKind, MemFlags, TypeKind},
traits::{
builder::BlockBuilderMethods, constants::ConstValueBuilderMethods, layout::LayoutMethods,
ty::TypeBuilderMethods,
Expand Down Expand Up @@ -147,6 +147,41 @@ impl<'a, 'b, Builder: BlockBuilderMethods<'a, 'b>> FnBuilder<'a, 'b, Builder> {
let operand = self.codegen_operand(builder, operand);
operand.value.store(builder, destination);
}
ir::RValue::Repeat(op, repeat) => {
let op = self.codegen_operand(builder, op);

// We don't write anything into the destination if it is a ZST... i.e. being
// ignored!
if destination.info.is_zst() {
return;
}

// Optimisation for immediates, we can use llvm.memset.p0i8.* to
// initialise the memory.
if let OperandValue::Immediate(val) = op.value {
let zero = builder.const_usize(0);
let start = destination.project_index(builder, zero).value;
let size = builder.const_usize(destination.info.size().bytes());

// If it is zero, we just fill the array with zeroes...
if self.ctx.const_to_optional_u128(val, false) == Some(0) {
let fill = self.ctx.const_u8(0);
builder.memset(start, fill, size, destination.alignment, MemFlags::empty());
return;
}

// If the type is the size of a `i8`, we just memset it with the value...
let v = builder.value_from_immediate(val);
if self.ctx.ty_of_value(v) == self.ctx.type_i8() {
builder.memset(start, v, size, destination.alignment, MemFlags::empty());
return;
}
}

// Slow-path: we actually need to generate code to write the value into the
// array...
builder.write_operand_repeatedly(op, *repeat, destination)
}
ir::RValue::Aggregate(kind, operands) => {
let destination = match *kind {
ir::AggregateKind::Enum(_, variant) => {
Expand Down Expand Up @@ -335,7 +370,7 @@ impl<'a, 'b, Builder: BlockBuilderMethods<'a, 'b>> FnBuilder<'a, 'b, Builder> {
let value = OperandValue::Immediate(new_value);
OperandRef { value, info: cast_ty }
}
ir::RValue::Aggregate(_, _) => {
ir::RValue::Repeat(_, _) | ir::RValue::Aggregate(_, _) => {
// This is only called if the aggregate value is a ZST, so we just
// create a new ZST operand...
OperandRef::zst(builder.layout_of(self.ty_of_rvalue(rvalue)))
Expand Down Expand Up @@ -546,7 +581,7 @@ impl<'a, 'b, Builder: BlockBuilderMethods<'a, 'b>> FnBuilder<'a, 'b, Builder> {
| ir::RValue::Ref(_, _, _)
| ir::RValue::Cast(_, _, _)
| ir::RValue::Discriminant(_) => true,
ir::RValue::Aggregate(_, _) => {
ir::RValue::Repeat(_, _) | ir::RValue::Aggregate(_, _) => {
// check if the type is a ZST, and if so this satisfies the
// case that the rvalue creates an operand...
let ty = self.ty_of_rvalue(rvalue);
Expand Down
Loading

0 comments on commit f64c50a

Please sign in to comment.