Skip to content

Commit

Permalink
feat(acir-simulator): advanced debug formatting for noir + acir simul…
Browse files Browse the repository at this point in the history
…ator (#775)

* advanced debug formatting for noir + acir simulator

* formatting

* comment out debug log in noir

* cleanup

* Update set.nr

* Remove commented out debug in set.nr

* Examples and warnings in debug log
  • Loading branch information
dbanks12 authored Jun 8, 2023
1 parent f564c48 commit 9196bdc
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 11 deletions.
74 changes: 74 additions & 0 deletions yarn-project/acir-simulator/src/client/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { ACVMField } from '../acvm/index.js';

/**
* Convert an array of ACVMFields to a string.
*
* @param msg - array of ACVMFields where each represents a single ascii character
* @returns string representation of the message
*/
function acvmFieldMessageToString(msg: ACVMField[]): string {
let msgStr = '';
for (const msgChar of msg) {
const asciiCode = Number(msgChar);
const asciiChar = String.fromCharCode(asciiCode);
msgStr = msgStr.concat(asciiChar);
}
// cut off string in case of preemptive null termination
const nullCharIndex = msgStr.indexOf('\\0');
if (nullCharIndex >= 0) {
msgStr = msgStr.substring(0, nullCharIndex);
}
return msgStr.replaceAll('\\n', '\n').replaceAll('\\t', '\t');
}

/**
* Format a debug string for Noir filling in `'{0}'` entries with their
* corresponding values from the args array.
*
* @param formatStr - str of form `'this is a string with some entries like {0} and {1}'`
* @param args - array of fields to fill in the string format entries with
* @returns formatted string
*/
function applyStringFormatting(formatStr: string, args: ACVMField[]): string {
const matches = formatStr.match(/{\d+}/g);
if (matches == null) {
return formatStr;
}
// Get the numeric values within the curly braces, convert them to numbers,
// and find the maximum value.
const maxIndex = Math.max(...matches.map(match => Number(match.slice(1, -1))));
const argsPadded = args.concat(Array.from({ length: Math.max(0, maxIndex - args.length) }, () => '0xBAD'));

return formatStr.replace(/{(\d+)}/g, function (match, index) {
return typeof args[index] != 'undefined' ? argsPadded[index] : match;
});
}

/**
* Convert an array of ACVMFields from ACVM to a formatted string.
*
* @param fields - either a single field to be printed, or a string to be formatted.
* When it is a string to be formatted:
* The last entry in `fields` is `numArgs` (the number of formatting
* args). The `formatArgs` occupy the end of the `fields` array,
* excluding that last entry (`numArgs`). The message string `msg`
* takes up the remaining entries at the start of the `fields` array.
*
* @returns formatted string
*/
export function fieldsToFormattedStr(fields: ACVMField[]): string {
if (fields.length === 1) {
return `${fields[0]}`;
} else {
const numArgs = Number(fields[fields.length - 1]);
const msgLen = fields.length - 1 - numArgs;

const msgFields = fields.slice(0, msgLen);
const formatArgs = fields.slice(msgLen, fields.length - 1);

const msg = acvmFieldMessageToString(msgFields);
const formattedMsg = applyStringFormatting(msg, formatArgs);

return formattedMsg;
}
}
6 changes: 3 additions & 3 deletions yarn-project/acir-simulator/src/client/private_execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
toAcvmEnqueuePublicFunctionResult,
} from '../acvm/index.js';
import { sizeOfType } from '../index.js';
import { fieldsToFormattedStr } from './debug.js';
import { ClientTxExecutionContext } from './client_execution_context.js';
import { Tuple, assertLength } from '@aztec/foundation/serialize';

Expand Down Expand Up @@ -173,9 +174,8 @@ export class PrivateFunctionExecution {
},
getL1ToL2Message: ([msgKey]: ACVMField[]) => this.context.getL1ToL2Message(fromACVMField(msgKey)),

debugLog: ([data]: ACVMField[]) => {
// eslint-disable-next-line
console.log(data);
debugLog: (fields: ACVMField[]) => {
this.log(fieldsToFormattedStr(fields));
return Promise.resolve([ZERO_ACVM_FIELD]);
},
enqueuePublicFunctionCall: async ([acvmContractAddress, acvmFunctionSelector, ...acvmArgs]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ClientTxExecutionContext } from './client_execution_context.js';
import { select_return_flattened as selectReturnFlattened } from '@noir-lang/noir_util_wasm';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { Fr } from '@aztec/foundation/fields';
import { fieldsToFormattedStr } from './debug.js';

const notAvailable = () => {
return Promise.reject(new Error(`Not available for unconstrained function execution`));
Expand Down Expand Up @@ -55,9 +56,8 @@ export class UnconstrainedFunctionExecution {
frToNumber(fromACVMField(acvmLimit)),
frToNumber(fromACVMField(acvmOffset)),
),
debugLog: ([data]: ACVMField[]) => {
// eslint-disable-next-line
console.log(data);
debugLog: (fields: ACVMField[]) => {
this.log(fieldsToFormattedStr(fields));
return Promise.resolve([ZERO_ACVM_FIELD]);
},
getL1ToL2Message: ([msgKey]: ACVMField[]) => this.context.getL1ToL2Message(fromACVMField(msgKey)),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@

// Utility function to console.log data in the acir simulator
// WARNING: sometimes when using debug logs the ACVM errors with: `thrown: "solver opcode resolution error: cannot solve opcode: expression has too many unknowns x155"`

#[oracle(debugLog)]
fn debug_log_oracle(_msg: Field) -> Field {}
fn debug_log_oracle<T, N>(_msg: T, _num_args: Field) -> Field {}
#[oracle(debugLog)]
fn debug_log_format_oracle<T, N>(_msg: T, _args: [Field; N], _num_args: Field) -> Field {}
#[oracle(debugLog)]
fn debug_log_field_oracle(_field: Field) -> Field {}

/// NOTE: call this with a str<N> msg of length > 1
/// Example:
/// `debug_log("blah blah this is a debug string");`
unconstrained fn debug_log<T>(msg: T) {
constrain debug_log_oracle(msg, 0) == 0;
}

/// NOTE: call this with a str<N> msg of form
/// "some string with {0} and {1} ... {N}"
/// and an array of N field which will be formatted
/// into the string in the simulator.
/// Example:
/// `debug_log_format("get_2(slot:{0}) =>\n\t0:{1}\n\t1:{2}", [storage_slot, notes.0.value, notes.1.value]);`
unconstrained fn debug_log_format<T, N>(msg: T, args: [Field; N]) {
constrain debug_log_format_oracle(msg, args, args.len()) == 0;
}

unconstrained fn debug_log(msg: Field) -> Field {
debug_log_oracle(msg)
}
/// Example:
/// `debug_log_field(my_field);`
unconstrained fn debug_log_field(field: Field) {
constrain debug_log_field_oracle(field) == 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ impl Set {
constrain note_getter_data.1.root == inputs.old_private_data_tree_root;

let notes = (note_getter_data.0.note, note_getter_data.1.note);

(context, notes)
}

Expand Down

0 comments on commit 9196bdc

Please sign in to comment.