Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement repeat expressions #992

Merged
merged 11 commits into from
Sep 25, 2023
Merged
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
Loading