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

feat: Add array_to_str_lossy #5613

Merged
merged 11 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ impl Context {
| Intrinsic::AsWitness
| Intrinsic::IsUnconstrained => {}
Intrinsic::ArrayLen
| Intrinsic::ArrayAsStrUnchecked
| Intrinsic::AsField
| Intrinsic::AsSlice
| Intrinsic::BlackBox(..)
Expand Down
4 changes: 4 additions & 0 deletions compiler/noirc_evaluator/src/ssa/ir/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub(crate) type InstructionId = Id<Instruction>;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub(crate) enum Intrinsic {
ArrayLen,
ArrayAsStrUnchecked,
AsSlice,
AssertConstant,
StaticAssert,
Expand All @@ -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"),
Expand Down Expand Up @@ -116,6 +118,7 @@ impl Intrinsic {
Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) => true,

Intrinsic::ArrayLen
| Intrinsic::ArrayAsStrUnchecked
| Intrinsic::AsSlice
| Intrinsic::SlicePushBack
| Intrinsic::SlicePushFront
Expand Down Expand Up @@ -144,6 +147,7 @@ impl Intrinsic {
pub(crate) fn lookup(name: &str) -> Option<Intrinsic> {
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),
Expand Down
2 changes: 2 additions & 0 deletions compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions compiler/noirc_evaluator/src/ssa/ir/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ impl Type {
Type::unsigned(8)
}

/// Creates the str<N> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ impl Context {
| Intrinsic::SliceRemove => true,

Intrinsic::ArrayLen
| Intrinsic::ArrayAsStrUnchecked
| Intrinsic::AssertConstant
| Intrinsic::StaticAssert
| Intrinsic::ApplyRangeConstraint
Expand Down
1 change: 1 addition & 0 deletions compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ fn slice_capacity_change(
| Intrinsic::StaticAssert
| Intrinsic::ApplyRangeConstraint
| Intrinsic::ArrayLen
| Intrinsic::ArrayAsStrUnchecked
| Intrinsic::StrAsBytes
| Intrinsic::BlackBox(_)
| Intrinsic::FromField
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
}
Expand Down
16 changes: 16 additions & 0 deletions compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,6 +39,7 @@ impl<'local, 'context> Interpreter<'local, 'context> {
) -> IResult<Value> {
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)),
Expand Down Expand Up @@ -112,6 +115,19 @@ fn array_len(
}
}

fn array_as_str_unchecked(
interner: &NodeInterner,
mut arguments: Vec<(Value, Location)>,
location: Location,
) -> IResult<Value> {
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)>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@ pub(crate) fn get_field(value: Value, location: Location) -> IResult<FieldElemen
}
}

pub(crate) fn get_u8(value: Value, location: Location) -> IResult<u8> {
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<u32> {
match value {
Value::U32(value) => Ok(value),
Expand Down
20 changes: 20 additions & 0 deletions docs/docs/noir/concepts/data_types/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
jfecher marked this conversation as resolved.
Show resolved Hide resolved

```rust
impl<let N: u32> [u8; N] {
pub fn as_str_unchecked(self) -> str<N>
}
```

example:

```rust
fn main() {
let hi = [104, 105].as_str_unchecked();
assert_eq(hi, "hi");
}
```
17 changes: 15 additions & 2 deletions noir_stdlib/src/array.nr
Original file line number Diff line number Diff line change
@@ -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, let N: u32> [T; N] {
#[builtin(array_len)]
pub fn len(self) -> u32 {}
Expand Down Expand Up @@ -108,6 +108,13 @@ impl<T, let N: u32> [T; N] {
}
}

impl<let N: u32> [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<N> {}
}

// 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<let N: u32>(a: [u32; N], find: u32) -> u32 {
Expand All @@ -119,3 +126,9 @@ unconstrained fn find_index<let N: u32>(a: [u32; N], find: u32) -> u32 {
}
result
}

impl<let N: u32> From<str<N>> for [u8; N] {
fn from(s: str<N>) -> Self {
s.as_bytes()
}
}
8 changes: 8 additions & 0 deletions noir_stdlib/src/string.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::collections::vec::Vec;
use crate::convert::From;

impl<let N: u32> str<N> {
/// Converts the given string into a byte array
#[builtin(str_as_bytes)]
Expand All @@ -9,3 +11,9 @@ impl<let N: u32> str<N> {
Vec::from_slice(self.as_bytes().as_slice())
}
}

impl<let N: u32> From<[u8; N]> for str<N> {
fn from(bytes: [u8; N]) -> Self {
bytes.as_str_unchecked()
}
}
Loading