diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 0e4bbbf759c..46dc908f430 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -2714,7 +2714,22 @@ impl<'a> Context<'a> { .get_or_create_witness_var(input) .map(|val| self.convert_vars_to_values(vec![val], dfg, result_ids))?) } - _ => todo!("expected a black box function"), + Intrinsic::ArrayAsStrUnchecked => Ok(vec![self.convert_value(arguments[0], dfg)]), + Intrinsic::AssertConstant => { + unreachable!("Expected assert_constant to be removed by this point") + } + Intrinsic::StaticAssert => { + unreachable!("Expected static_assert to be removed by this point") + } + Intrinsic::StrAsBytes => unreachable!("Expected as_bytes to be removed by this point"), + Intrinsic::FromField => unreachable!("Expected from_field to be removed by this point"), + Intrinsic::AsField => unreachable!("Expected as_field to be removed by this point"), + Intrinsic::IsUnconstrained => { + unreachable!("Expected is_unconstrained to be removed by this point") + } + Intrinsic::DerivePedersenGenerators => { + unreachable!("DerivePedersenGenerators can only be called with constants") + } } } diff --git a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs index 5831faa7c4d..24fcb8f61df 100644 --- a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs +++ b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs @@ -202,6 +202,7 @@ impl Context { | Intrinsic::AsWitness | Intrinsic::IsUnconstrained => {} Intrinsic::ArrayLen + | Intrinsic::ArrayAsStrUnchecked | Intrinsic::AsField | Intrinsic::AsSlice | Intrinsic::BlackBox(..) diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 0573b1c9ce1..35b114fd115 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -51,6 +51,7 @@ pub(crate) type InstructionId = Id; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub(crate) enum Intrinsic { ArrayLen, + ArrayAsStrUnchecked, AsSlice, AssertConstant, StaticAssert, @@ -76,6 +77,7 @@ impl std::fmt::Display for Intrinsic { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Intrinsic::ArrayLen => write!(f, "array_len"), + Intrinsic::ArrayAsStrUnchecked => write!(f, "array_as_str_unchecked"), Intrinsic::AsSlice => write!(f, "as_slice"), Intrinsic::AssertConstant => write!(f, "assert_constant"), Intrinsic::StaticAssert => write!(f, "static_assert"), @@ -116,6 +118,7 @@ impl Intrinsic { Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) => true, Intrinsic::ArrayLen + | Intrinsic::ArrayAsStrUnchecked | Intrinsic::AsSlice | Intrinsic::SlicePushBack | Intrinsic::SlicePushFront @@ -144,6 +147,7 @@ impl Intrinsic { pub(crate) fn lookup(name: &str) -> Option { match name { "array_len" => Some(Intrinsic::ArrayLen), + "array_as_str_unchecked" => Some(Intrinsic::ArrayAsStrUnchecked), "as_slice" => Some(Intrinsic::AsSlice), "assert_constant" => Some(Intrinsic::AssertConstant), "static_assert" => Some(Intrinsic::StaticAssert), diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index ad01edbd0b2..08f145f598f 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -85,6 +85,8 @@ pub(super) fn simplify_call( SimplifyResult::None } } + // Strings are already arrays of bytes in SSA + Intrinsic::ArrayAsStrUnchecked => SimplifyResult::SimplifiedTo(arguments[0]), Intrinsic::AsSlice => { let array = dfg.get_array_constant(arguments[0]); if let Some((array, array_type)) = array { diff --git a/compiler/noirc_evaluator/src/ssa/ir/types.rs b/compiler/noirc_evaluator/src/ssa/ir/types.rs index 7e62883f57c..e467fa5400d 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/types.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/types.rs @@ -110,6 +110,11 @@ impl Type { Type::unsigned(8) } + /// Creates the str type, of the given length N + pub(crate) fn str(length: usize) -> Type { + Type::Array(Rc::new(vec![Type::char()]), length) + } + /// Creates the native field type. pub(crate) fn field() -> Type { Type::Numeric(NumericType::NativeField) diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs index 1584b848564..224060e131f 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -158,6 +158,7 @@ impl Context { | Intrinsic::SliceRemove => true, Intrinsic::ArrayLen + | Intrinsic::ArrayAsStrUnchecked | Intrinsic::AssertConstant | Intrinsic::StaticAssert | Intrinsic::ApplyRangeConstraint diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs index 242eea7d6f4..c70760de85f 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs @@ -229,6 +229,7 @@ fn slice_capacity_change( | Intrinsic::StaticAssert | Intrinsic::ApplyRangeConstraint | Intrinsic::ArrayLen + | Intrinsic::ArrayAsStrUnchecked | Intrinsic::StrAsBytes | Intrinsic::BlackBox(_) | Intrinsic::FromField diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index 8e55debec1d..e16f6697c70 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -242,7 +242,7 @@ impl<'a> FunctionContext<'a> { ast::Type::Integer(Signedness::Signed, bits) => Type::signed((*bits).into()), ast::Type::Integer(Signedness::Unsigned, bits) => Type::unsigned((*bits).into()), ast::Type::Bool => Type::unsigned(1), - ast::Type::String(len) => Type::Array(Rc::new(vec![Type::char()]), *len as usize), + ast::Type::String(len) => Type::str(*len as usize), ast::Type::FmtString(_, _) => { panic!("convert_non_tuple_type called on a fmt string: {typ}") } diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index c12fb4d1113..d059e1ae4de 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -23,6 +23,8 @@ use crate::{ QuotedType, Shared, Type, }; +use self::builtin_helpers::{get_array, get_u8}; + use super::Interpreter; pub(crate) mod builtin_helpers; @@ -37,6 +39,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { ) -> IResult { let interner = &mut self.elaborator.interner; match name { + "array_as_str_unchecked" => array_as_str_unchecked(interner, arguments, location), "array_len" => array_len(interner, arguments, location), "as_slice" => as_slice(interner, arguments, location), "is_unconstrained" => Ok(Value::Bool(true)), @@ -112,6 +115,19 @@ fn array_len( } } +fn array_as_str_unchecked( + interner: &NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(1, &arguments, location)?; + + let array = get_array(interner, arguments.pop().unwrap().0, location)?.0; + let string_bytes = try_vecmap(array, |byte| get_u8(byte, location))?; + let string = String::from_utf8_lossy(&string_bytes).into_owned(); + Ok(Value::String(Rc::new(string))) +} + fn as_slice( interner: &NodeInterner, arguments: Vec<(Value, Location)>, diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs index 1c73f946587..d8b647bdb79 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs @@ -105,6 +105,17 @@ pub(crate) fn get_field(value: Value, location: Location) -> IResult IResult { + match value { + Value::U8(value) => Ok(value), + value => { + let expected = Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight); + let actual = value.get_type().into_owned(); + Err(InterpreterError::TypeMismatch { expected, actual, location }) + } + } +} + pub(crate) fn get_u32(value: Value, location: Location) -> IResult { match value { Value::U32(value) => Ok(value), diff --git a/docs/docs/noir/concepts/data_types/arrays.md b/docs/docs/noir/concepts/data_types/arrays.md index 9a4ab5d3c1f..e5e9f5a1d3b 100644 --- a/docs/docs/noir/concepts/data_types/arrays.md +++ b/docs/docs/noir/concepts/data_types/arrays.md @@ -251,3 +251,23 @@ fn main() { } ``` + +### as_str_unchecked + +Converts a byte array of type `[u8; N]` to a string. Note that this performs no UTF-8 validation - +the given array is interpreted as-is as a string. + +```rust +impl [u8; N] { + pub fn as_str_unchecked(self) -> str +} +``` + +example: + +```rust +fn main() { + let hi = [104, 105].as_str_unchecked(); + assert_eq(hi, "hi"); +} +``` diff --git a/noir_stdlib/src/array.nr b/noir_stdlib/src/array.nr index ad9c7093d07..af2bea12c60 100644 --- a/noir_stdlib/src/array.nr +++ b/noir_stdlib/src/array.nr @@ -1,7 +1,7 @@ use crate::cmp::Ord; +use crate::option::Option; +use crate::convert::From; -// TODO: Once we fully move to the new SSA pass this module can be removed and replaced -// by the methods in the `slice` module impl [T; N] { #[builtin(array_len)] pub fn len(self) -> u32 {} @@ -108,6 +108,13 @@ impl [T; N] { } } +impl [u8; N] { + /// Convert a sequence of bytes as-is into a string. + /// This function performs no UTF-8 validation or similar. + #[builtin(array_as_str_unchecked)] + pub fn as_str_unchecked(self) -> str {} +} + // helper function used to look up the position of a value in an array of Field // Note that function returns 0 if the value is not found unconstrained fn find_index(a: [u32; N], find: u32) -> u32 { @@ -119,3 +126,9 @@ unconstrained fn find_index(a: [u32; N], find: u32) -> u32 { } result } + +impl From> for [u8; N] { + fn from(s: str) -> Self { + s.as_bytes() + } +} diff --git a/noir_stdlib/src/string.nr b/noir_stdlib/src/string.nr index 5f8f3de775d..18fb449626a 100644 --- a/noir_stdlib/src/string.nr +++ b/noir_stdlib/src/string.nr @@ -1,4 +1,6 @@ use crate::collections::vec::Vec; +use crate::convert::From; + impl str { /// Converts the given string into a byte array #[builtin(str_as_bytes)] @@ -9,3 +11,9 @@ impl str { Vec::from_slice(self.as_bytes().as_slice()) } } + +impl From<[u8; N]> for str { + fn from(bytes: [u8; N]) -> Self { + bytes.as_str_unchecked() + } +}