Skip to content

Commit bf5da90

Browse files
authored
Add support for builtin functions (#2508)
This PR adds the necessary infrastructure to support builtin functions in the lowerer and prepares the groundwork for providing language service support for const evaluated expressions. These changes are related because builtin function calls are a kind of const evaluated expression. The PR can be understood in two areas: 1. The part that prepares the groundwork for providing language service support for const evaluated expressions, which includes two changes: const expressions are now eagerly evaluated in the lowerer; and the `Expr` type is augmented with the result of const evaluating the expression, instead of just storing the final value in the AST, this will provide the language service with the necessary span and type information for its hover, jump to definition, and type hint features. 2. The part that adds the necessary infrastructure to support builtin functions in the lowerer: this changes include adding a polymorphic dispatch mechanism for QASM bultins and wiring this mechanism to the `Lowerer::lower_function_call_expr` method.
1 parent 180d8cc commit bf5da90

33 files changed

+1503
-529
lines changed

compiler/qsc_qasm/src/compiler.rs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -816,17 +816,19 @@ impl QasmCompiler {
816816
build_path_ident_expr("ApplyOperationPowerA", modifier.span, stmt.span);
817817
}
818818
semast::GateModifierKind::Ctrl(num_ctrls) => {
819+
let num_ctrls = num_ctrls.get_const_u32()?;
820+
819821
// remove the last n qubits from the qubit list
820-
if qubits.len() < *num_ctrls as usize {
822+
if qubits.len() < num_ctrls as usize {
821823
let kind = CompilerErrorKind::InvalidNumberOfQubitArgs(
822-
*num_ctrls as usize,
824+
num_ctrls as usize,
823825
qubits.len(),
824826
modifier.span,
825827
);
826828
self.push_compiler_error(kind);
827829
return None;
828830
}
829-
let ctrl = qubits.split_off(qubits.len().saturating_sub(*num_ctrls as usize));
831+
let ctrl = qubits.split_off(qubits.len().saturating_sub(num_ctrls as usize));
830832
let ctrls = build_expr_array_expr(ctrl, modifier.span);
831833
args = build_tuple_expr(vec![ctrls, args]);
832834
callee = build_unary_op_expr(
@@ -836,17 +838,19 @@ impl QasmCompiler {
836838
);
837839
}
838840
semast::GateModifierKind::NegCtrl(num_ctrls) => {
841+
let num_ctrls = num_ctrls.get_const_u32()?;
842+
839843
// remove the last n qubits from the qubit list
840-
if qubits.len() < *num_ctrls as usize {
844+
if qubits.len() < num_ctrls as usize {
841845
let kind = CompilerErrorKind::InvalidNumberOfQubitArgs(
842-
*num_ctrls as usize,
846+
num_ctrls as usize,
843847
qubits.len(),
844848
modifier.span,
845849
);
846850
self.push_compiler_error(kind);
847851
return None;
848852
}
849-
let ctrl = qubits.split_off(qubits.len().saturating_sub(*num_ctrls as usize));
853+
let ctrl = qubits.split_off(qubits.len().saturating_sub(num_ctrls as usize));
850854
let ctrls = build_expr_array_expr(ctrl, modifier.span);
851855
let lit_0 = build_lit_int_expr(0, Span::default());
852856
args = build_tuple_expr(vec![lit_0, callee, ctrls, args]);
@@ -1024,11 +1028,12 @@ impl QasmCompiler {
10241028

10251029
let stmt = match self.config.qubit_semantics {
10261030
QubitSemantics::QSharp => {
1027-
managed_qubit_alloc_array(name, stmt.size, stmt.span, name_span, stmt.size_span)
1031+
let size = stmt.size.get_const_u32()?;
1032+
managed_qubit_alloc_array(name, size, stmt.span, name_span, stmt.size_span)
10281033
}
10291034
QubitSemantics::Qiskit => build_unmanaged_qubit_alloc_array(
10301035
name,
1031-
stmt.size,
1036+
stmt.size.get_const_u32()?,
10321037
stmt.span,
10331038
name_span,
10341039
stmt.size_span,
@@ -1143,6 +1148,12 @@ impl QasmCompiler {
11431148
}
11441149

11451150
fn compile_expr(&mut self, expr: &semast::Expr) -> qsast::Expr {
1151+
if expr.ty.is_const() {
1152+
if let Some(value) = expr.get_const_value() {
1153+
return self.compile_literal_expr(&value, expr.span);
1154+
}
1155+
}
1156+
11461157
match expr.kind.as_ref() {
11471158
semast::ExprKind::Err => qsast::Expr {
11481159
span: expr.span,
@@ -1162,6 +1173,13 @@ impl QasmCompiler {
11621173
semast::ExprKind::FunctionCall(function_call) => {
11631174
self.compile_function_call_expr(function_call)
11641175
}
1176+
semast::ExprKind::BuiltinFunctionCall(_) => {
1177+
let Some(value) = expr.get_const_value() else {
1178+
unreachable!("builtin function call exprs are only lowered if they succeed");
1179+
};
1180+
1181+
self.compile_literal_expr(&value, expr.span)
1182+
}
11651183
semast::ExprKind::Cast(cast) => self.compile_cast_expr(cast),
11661184
semast::ExprKind::IndexExpr(index_expr) => self.compile_index_expr(index_expr),
11671185
semast::ExprKind::Paren(pexpr) => self.compile_paren_expr(pexpr, expr.span),

compiler/qsc_qasm/src/semantic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::io::InMemorySourceResolver;
55
use crate::io::SourceResolver;
66
use crate::parser::QasmSource;
77

8-
use lowerer::Lowerer;
8+
pub(crate) use lowerer::Lowerer;
99
use qsc_frontend::compile::SourceMap;
1010
use qsc_frontend::error::WithSource;
1111

compiler/qsc_qasm/src/semantic/ast.rs

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -479,17 +479,90 @@ impl Display for ExprStmt {
479479
pub struct Expr {
480480
pub span: Span,
481481
pub kind: Box<ExprKind>,
482+
pub const_value: Option<LiteralKind>,
482483
pub ty: super::types::Type,
483484
}
484485

485486
impl Display for Expr {
486487
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
487488
writeln_header(f, "Expr", self.span)?;
488489
writeln_field(f, "ty", &self.ty)?;
490+
if self.const_value.is_some() {
491+
writeln_opt_field(f, "const_value", self.const_value.as_ref())?;
492+
}
489493
write_field(f, "kind", &self.kind)
490494
}
491495
}
492496

497+
impl Expr {
498+
pub fn new(span: Span, kind: ExprKind, ty: super::types::Type) -> Self {
499+
Self {
500+
span,
501+
kind: kind.into(),
502+
ty,
503+
const_value: None,
504+
}
505+
}
506+
507+
pub fn int(val: i64, span: Span) -> Self {
508+
let val = LiteralKind::Int(val);
509+
Expr {
510+
span,
511+
kind: Box::new(ExprKind::Lit(val.clone())),
512+
ty: super::types::Type::Int(None, true),
513+
const_value: Some(val),
514+
}
515+
}
516+
517+
pub fn uint(val: i64, span: Span) -> Self {
518+
let val = LiteralKind::Int(val);
519+
Expr {
520+
span,
521+
kind: Box::new(ExprKind::Lit(val.clone())),
522+
ty: super::types::Type::UInt(None, true),
523+
const_value: Some(val),
524+
}
525+
}
526+
527+
pub fn float(val: f64, span: Span) -> Self {
528+
let val = LiteralKind::Float(val);
529+
Expr {
530+
span,
531+
kind: Box::new(ExprKind::Lit(val.clone())),
532+
ty: super::types::Type::Float(None, true),
533+
const_value: Some(val),
534+
}
535+
}
536+
537+
pub fn builtin_funcall(
538+
name: &str,
539+
span: Span,
540+
fn_name_span: Span,
541+
function_ty: crate::semantic::types::Type,
542+
args: &[Expr],
543+
output: LiteralKind,
544+
) -> Self {
545+
let crate::semantic::types::Type::Function(_, ty) = &function_ty else {
546+
unreachable!("if we hit this there is a bug in the builtin functions implementation");
547+
};
548+
549+
let ty = ty.as_ref().clone();
550+
551+
Self {
552+
span,
553+
kind: Box::new(ExprKind::BuiltinFunctionCall(BuiltinFunctionCall {
554+
span,
555+
fn_name_span,
556+
name: name.into(),
557+
args: args.into(),
558+
function_ty,
559+
})),
560+
ty,
561+
const_value: Some(output),
562+
}
563+
}
564+
}
565+
493566
#[derive(Clone, Debug)]
494567
pub struct Set {
495568
pub span: Span,
@@ -539,8 +612,12 @@ impl Display for QuantumGateModifier {
539612
pub enum GateModifierKind {
540613
Inv,
541614
Pow(Expr),
542-
Ctrl(u32),
543-
NegCtrl(u32),
615+
/// This `Expr` is const, but we don't substitute by the `LiteralKind` yet
616+
/// to be able to provide Span and Type information to the Language Service.
617+
Ctrl(Expr),
618+
/// This `Expr` is const, but we don't substitute by the `LiteralKind` yet
619+
/// to be able to provide Span and Type information to the Language Service.
620+
NegCtrl(Expr),
544621
}
545622

546623
impl Display for GateModifierKind {
@@ -692,7 +769,9 @@ impl Display for QubitDeclaration {
692769
pub struct QubitArrayDeclaration {
693770
pub span: Span,
694771
pub symbol_id: SymbolId,
695-
pub size: u32,
772+
/// This `Expr` is const, but we don't substitute by the `LiteralKind` yet
773+
/// to be able to provide Span and Type information to the Language Service.
774+
pub size: Expr,
696775
pub size_span: Span,
697776
}
698777

@@ -991,6 +1070,7 @@ pub enum ExprKind {
9911070
BinaryOp(BinaryOpExpr),
9921071
Lit(LiteralKind),
9931072
FunctionCall(FunctionCall),
1073+
BuiltinFunctionCall(BuiltinFunctionCall),
9941074
Cast(Cast),
9951075
IndexExpr(IndexExpr),
9961076
Paren(Expr),
@@ -1007,6 +1087,7 @@ impl Display for ExprKind {
10071087
ExprKind::BinaryOp(expr) => write!(f, "{expr}"),
10081088
ExprKind::Lit(lit) => write!(f, "Lit: {lit}"),
10091089
ExprKind::FunctionCall(call) => write!(f, "{call}"),
1090+
ExprKind::BuiltinFunctionCall(call) => write!(f, "{call}"),
10101091
ExprKind::Cast(expr) => write!(f, "{expr}"),
10111092
ExprKind::IndexExpr(expr) => write!(f, "{expr}"),
10121093
ExprKind::Paren(expr) => write!(f, "Paren {expr}"),
@@ -1104,6 +1185,29 @@ impl Display for FunctionCall {
11041185
}
11051186
}
11061187

1188+
/// The information in this struct is aimed to be consumed
1189+
/// by the language service. The result of the computation
1190+
/// is already stored in the [`Expr::const_value`] field by
1191+
/// the time the `Expr` is created.
1192+
#[derive(Clone, Debug)]
1193+
pub struct BuiltinFunctionCall {
1194+
pub span: Span,
1195+
pub fn_name_span: Span,
1196+
pub name: Rc<str>,
1197+
pub function_ty: crate::semantic::types::Type,
1198+
pub args: Rc<[Expr]>,
1199+
}
1200+
1201+
impl Display for BuiltinFunctionCall {
1202+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1203+
writeln_header(f, "BuiltinFunctionCall", self.span)?;
1204+
writeln_field(f, "fn_name_span", &self.fn_name_span)?;
1205+
writeln_field(f, "name", &self.name)?;
1206+
writeln_field(f, "function_ty", &self.function_ty)?;
1207+
write_list_field(f, "args", self.args.iter())
1208+
}
1209+
}
1210+
11071211
#[derive(Clone, Debug)]
11081212
pub struct Cast {
11091213
pub span: Span,
@@ -1216,11 +1320,11 @@ impl Array {
12161320
data,
12171321
dims: (&dims[1..]).into(),
12181322
};
1219-
let expr = Expr {
1220-
span: Default::default(),
1221-
kind: Box::new(ExprKind::Lit(LiteralKind::Array(array))),
1222-
ty: super::types::Type::make_array_ty(&dims[1..], base_ty),
1223-
};
1323+
let expr = Expr::new(
1324+
Default::default(),
1325+
ExprKind::Lit(LiteralKind::Array(array)),
1326+
super::types::Type::make_array_ty(&dims[1..], base_ty),
1327+
);
12241328
let dim_size = dims[0] as usize;
12251329
vec![expr; dim_size]
12261330
}

compiler/qsc_qasm/src/semantic/const_eval.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use thiserror::Error;
2323

2424
#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)]
2525
pub enum ConstEvalError {
26-
#[error("division by error during const evaluation")]
26+
#[error("division by zero error during const evaluation")]
2727
#[diagnostic(code("Qasm.Lowerer.DivisionByZero"))]
2828
DivisionByZero(#[label] Span),
2929
#[error("expression must be const")]
@@ -38,6 +38,9 @@ pub enum ConstEvalError {
3838
#[error("uint expression must evaluate to a non-negative value, but it evaluated to {0}")]
3939
#[diagnostic(code("Qasm.Lowerer.NegativeUIntValue"))]
4040
NegativeUIntValue(i64, #[label] Span),
41+
#[error("{0}")]
42+
#[diagnostic(code("Qasm.Lowerer.NoValidOverloadForBuiltinFunction"))]
43+
NoValidOverloadForBuiltinFunction(String, #[label] Span),
4144
#[error("too many indices provided")]
4245
#[diagnostic(code("Qasm.Lowerer.TooManyIndices"))]
4346
TooManyIndices(#[label] Span),
@@ -50,10 +53,33 @@ pub enum ConstEvalError {
5053
}
5154

5255
impl Expr {
56+
/// A builder pattern that initializes the [`Expr::const_value`] field
57+
/// to the result of const evaluating the expression.
58+
pub(crate) fn with_const_value(mut self, ctx: &mut Lowerer) -> Self {
59+
self.const_value = self.const_eval(ctx);
60+
self
61+
}
62+
63+
pub(crate) fn get_const_value(&self) -> Option<LiteralKind> {
64+
self.const_value.clone()
65+
}
66+
67+
pub(crate) fn get_const_u32(&self) -> Option<u32> {
68+
if let Some(LiteralKind::Int(val)) = self.get_const_value() {
69+
u32::try_from(val).ok()
70+
} else {
71+
None
72+
}
73+
}
74+
5375
/// Tries to evaluate the expression. It takes the current `Lowerer` as
5476
/// the evaluation context to resolve symbols and push errors in case
5577
/// of failure.
56-
pub(crate) fn const_eval(&self, ctx: &mut Lowerer) -> Option<LiteralKind> {
78+
fn const_eval(&self, ctx: &mut Lowerer) -> Option<LiteralKind> {
79+
if self.const_value.is_some() {
80+
return self.const_value.clone();
81+
}
82+
5783
let ty = &self.ty;
5884

5985
if ty.is_err() {
@@ -72,6 +98,7 @@ impl Expr {
7298
ExprKind::BinaryOp(binary_op_expr) => binary_op_expr.const_eval(ctx),
7399
ExprKind::Lit(literal_kind) => Some(literal_kind.clone()),
74100
ExprKind::FunctionCall(function_call) => function_call.const_eval(ctx, ty),
101+
ExprKind::BuiltinFunctionCall(_) => self.get_const_value(),
75102
ExprKind::Cast(cast) => cast.const_eval(ctx),
76103
ExprKind::IndexExpr(index_expr) => index_expr.const_eval(ctx, ty),
77104
ExprKind::Paren(expr) => expr.const_eval(ctx),
@@ -83,7 +110,10 @@ impl Expr {
83110

84111
impl SymbolId {
85112
fn const_eval(self, ctx: &mut Lowerer) -> Option<LiteralKind> {
86-
ctx.symbols[self].get_const_expr()?.const_eval(ctx)
113+
// Expressions associated to const symbols are evaluated
114+
// when they are pushed in the symbol table. So, we just
115+
// need to get the already computed value here.
116+
ctx.symbols[self].get_const_value()
87117
}
88118
}
89119

compiler/qsc_qasm/src/semantic/error.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ pub enum SemanticErrorKind {
3030
#[error("array literals are only allowed in classical declarations")]
3131
#[diagnostic(code("Qasm.Lowerer.ArrayLiteralInNonClassicalDecl"))]
3232
ArrayLiteralInNonClassicalDecl(#[label] Span),
33-
#[error("array size must be a non-negative integer const expression")]
34-
#[diagnostic(code("Qasm.Lowerer.ArraySizeMustBeNonNegativeConstExpr"))]
35-
ArraySizeMustBeNonNegativeConstExpr(#[label] Span),
3633
#[error("first quantum register is of type {0} but found an argument of type {1}")]
3734
#[diagnostic(code("Qasm.Lowerer.BroadcastCallQuantumArgsDisagreeInSize"))]
3835
BroadcastCallQuantumArgsDisagreeInSize(String, String, #[label] Span),
@@ -88,6 +85,15 @@ pub enum SemanticErrorKind {
8885
#[error("{0} must be a const expression")]
8986
#[diagnostic(code("Qasm.Lowerer.ExprMustBeConst"))]
9087
ExprMustBeConst(String, #[label] Span),
88+
#[error("{0} must be an integer")]
89+
#[diagnostic(code("Qasm.Lowerer.ExprMustBeInt"))]
90+
ExprMustBeInt(String, #[label] Span),
91+
#[error("{0} must be a non-negative integer")]
92+
#[diagnostic(code("Qasm.Lowerer.ExprMustBeNonNegativeInt"))]
93+
ExprMustBeNonNegativeInt(String, #[label] Span),
94+
#[error("{0} must be a positive integer")]
95+
#[diagnostic(code("Qasm.Lowerer.ExprMustBePositiveInt"))]
96+
ExprMustBePositiveInt(String, #[label] Span),
9197
#[error("{0} must fit in a u32")]
9298
#[diagnostic(code("Qasm.Lowerer.ExprMustFitInU32"))]
9399
ExprMustFitInU32(String, #[label] Span),
@@ -187,6 +193,10 @@ pub enum SemanticErrorKind {
187193
#[error("range expressions must have a stop when used in for loops")]
188194
#[diagnostic(code("Qasm.Lowerer.RangeExpressionsMustHaveStop"))]
189195
RangeExpressionsMustHaveStop(#[label] Span),
196+
#[error("redefined builtin function: {0}")]
197+
#[help("builtin functions cannot be redefined, try choosing another name")]
198+
#[diagnostic(code("Qasm.Lowerer.RedefinedBuiltinFunction"))]
199+
RedefinedBuiltinFunction(String, #[label] Span),
190200
#[error("redefined symbol: {0}")]
191201
#[diagnostic(code("Qasm.Lowerer.RedefinedSymbol"))]
192202
RedefinedSymbol(String, #[label] Span),
@@ -223,9 +233,6 @@ pub enum SemanticErrorKind {
223233
#[error("types differ by dimensions and are incompatible")]
224234
#[diagnostic(code("Qasm.Lowerer.TypeRankError"))]
225235
TypeRankError(#[label] Span),
226-
#[error("type width must be a positive integer const expression")]
227-
#[diagnostic(code("Qasm.Lowerer.TypeWidthMustBePositiveIntConstExpr"))]
228-
TypeWidthMustBePositiveIntConstExpr(#[label] Span),
229236
#[error("undefined symbol: {0}")]
230237
#[diagnostic(code("Qasm.Lowerer.UndefinedSymbol"))]
231238
UndefinedSymbol(String, #[label] Span),

0 commit comments

Comments
 (0)