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 3 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::ArrayToStrLossy => todo!("non-constant array_to_str_lossy"),
jfecher marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -15,12 +15,12 @@
impl Ssa {
/// Go through each top-level non-brillig function and detect if it has independent subgraphs
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn check_for_underconstrained_values(&mut self) -> Vec<SsaReport> {

Check warning on line 18 in compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (underconstrained)
let mut warnings: Vec<SsaReport> = Vec::new();
for function in self.functions.values() {
match function.runtime() {
RuntimeType::Acir { .. } => {
warnings.extend(check_for_underconstrained_values_within_function(

Check warning on line 23 in compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (underconstrained)
function,
&self.functions,
));
Expand All @@ -33,7 +33,7 @@
}

/// Detect independent subgraphs (not connected to function inputs or outputs) and return a vector of bug reports if some are found
fn check_for_underconstrained_values_within_function(

Check warning on line 36 in compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (underconstrained)
function: &Function,
all_functions: &BTreeMap<FunctionId, Function>,
) -> Vec<SsaReport> {
Expand Down Expand Up @@ -202,6 +202,7 @@
| Intrinsic::AsWitness
| Intrinsic::IsUnconstrained => {}
Intrinsic::ArrayLen
| Intrinsic::ArrayToStrLossy
| Intrinsic::AsField
| Intrinsic::AsSlice
| Intrinsic::BlackBox(..)
Expand Down Expand Up @@ -360,7 +361,7 @@
builder.terminate_with_return(vec![v2]);

let mut ssa = builder.finish();
let ssa_level_warnings = ssa.check_for_underconstrained_values();

Check warning on line 364 in compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (underconstrained)
assert_eq!(ssa_level_warnings.len(), 0);
}

Expand Down Expand Up @@ -405,7 +406,7 @@
let v2 = builder.insert_binary(v0, BinaryOp::Add, v1);
builder.terminate_with_return(vec![v2]);
let mut ssa = builder.finish();
let ssa_level_warnings = ssa.check_for_underconstrained_values();

Check warning on line 409 in compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (underconstrained)
assert_eq!(ssa_level_warnings.len(), 1);
}
}
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 @@ -50,6 +50,7 @@ pub(crate) type InstructionId = Id<Instruction>;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) enum Intrinsic {
ArrayLen,
ArrayToStrLossy,
AsSlice,
AssertConstant,
StaticAssert,
Expand All @@ -75,6 +76,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::ArrayToStrLossy => write!(f, "array_to_str"),
Intrinsic::AsSlice => write!(f, "as_slice"),
Intrinsic::AssertConstant => write!(f, "assert_constant"),
Intrinsic::StaticAssert => write!(f, "static_assert"),
Expand Down Expand Up @@ -115,6 +117,7 @@ impl Intrinsic {
Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) => true,

Intrinsic::ArrayLen
| Intrinsic::ArrayToStrLossy
| Intrinsic::AsSlice
| Intrinsic::SlicePushBack
| Intrinsic::SlicePushFront
Expand Down Expand Up @@ -143,6 +146,7 @@ impl Intrinsic {
pub(crate) fn lookup(name: &str) -> Option<Intrinsic> {
match name {
"array_len" => Some(Intrinsic::ArrayLen),
"array_to_str_lossy" => Some(Intrinsic::ArrayToStrLossy),
"as_slice" => Some(Intrinsic::AsSlice),
"assert_constant" => Some(Intrinsic::AssertConstant),
"static_assert" => Some(Intrinsic::StaticAssert),
Expand Down
32 changes: 31 additions & 1 deletion compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use fxhash::FxHashMap as HashMap;
use std::{collections::VecDeque, rc::Rc};
use std::{borrow::Cow, collections::VecDeque, rc::Rc};

use acvm::{acir::AcirField, acir::BlackBoxFunc, BlackBoxResolutionError, FieldElement};
use bn254_blackbox_solver::derive_generators;
Expand Down Expand Up @@ -85,6 +85,10 @@ pub(super) fn simplify_call(
SimplifyResult::None
}
}
Intrinsic::ArrayToStrLossy => match simplify_array_to_str_lossy(dfg, arguments[0]) {
Some(string) => SimplifyResult::SimplifiedTo(string),
None => SimplifyResult::None,
},
Intrinsic::AsSlice => {
let array = dfg.get_array_constant(arguments[0]);
if let Some((array, array_type)) = array {
Expand Down Expand Up @@ -321,6 +325,32 @@ pub(super) fn simplify_call(
}
}

fn simplify_array_to_str_lossy(dfg: &mut DataFlowGraph, value: ValueId) -> Option<ValueId> {
let array = dfg.get_array_constant(value)?.0;
let mut bytes = Vec::with_capacity(array.len());

for value in array {
let value = dfg.get_numeric_constant(value)?;
let char: u8 = value.try_to_u32().and_then(|x| x.try_into().ok())?;
bytes.push(char);
}

// Convert the string to UTF-8. If the string converted as-is with no changes then
// we can reuse the original value id since strings are represented as byte arrays in SSA.
match String::from_utf8_lossy(&bytes) {
Cow::Borrowed(_) => Some(value),
Cow::Owned(new_bytes) => {
let mut new_array = im::Vector::new();
for byte in new_bytes.bytes() {
let constant = (byte as u128).into();
new_array.push_back(dfg.make_constant(constant, Type::char()));
}
let typ = Type::str(new_array.len());
Some(dfg.make_array(new_array, typ))
}
}
}

/// Slices have a tuple structure (slice length, slice contents) to enable logic
/// that uses dynamic slice lengths (such as with merging slices in the flattening pass).
/// This method codegens an update to the slice length.
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 @@ -109,6 +109,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::ArrayToStrLossy
| 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::ArrayToStrLossy
| 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
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_lossy

Converts a byte array of type `[u8; N]` to a UTF-8 encoded string. Any invalid UTF-8 character
is replaced with a placeholder character in the returned string.

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

example:

```rust
fn main() {
let hi = [104, 105].as_str_lossy();
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 UTF-8 encoded sequence of bytes into a string.
/// Replaces any invalid UTF-8 characters with a placeholder character.
#[builtin(array_to_str_lossy)]
pub fn as_str_lossy(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_lossy()
}
}
Loading