Skip to content

Commit

Permalink
feat: add support for codegenning multiple functions which use the sa…
Browse files Browse the repository at this point in the history
…me structs in their interface (#3868)

# Description

## Problem\*

Resolves <!-- Link to GitHub Issue -->

## Summary\*

This PR allows `noir_codegen` to gracefully handle multiple functions
functions. Previously these would cause TS errors if they shared a
struct between their function signatures as it would be codegenned
twice.

## Additional Context



## Documentation\*

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[Exceptional Case]** Documentation to be submitted in a separate
PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
  • Loading branch information
TomAFrench authored Dec 19, 2023
1 parent 2529977 commit 1dcfcc5
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 50 deletions.
20 changes: 11 additions & 9 deletions tooling/noir_codegen/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AbiType } from '@noir-lang/noirc_abi';
import { CompiledCircuit } from '@noir-lang/types';
import { PrimitiveTypesUsed, generateTsInterface } from './noir_types.js';
import { PrimitiveTypesUsed, generateTsInterface, codegenStructDefinitions } from './noir_types.js';

// TODO: reenable this. See `abiTypeToTs` for reasoning.
// export type FixedLengthArray<T, L extends number> = L extends 0 ? never[]: T[] & { length: L };
Expand All @@ -19,26 +20,25 @@ const codegenFunction = (
const args = function_signature.inputs.map(([name]) => `${name}`).join(', ');
const args_with_types = function_signature.inputs.map(([name, type]) => `${name}: ${type}`).join(', ');

return `
export const ${name}_circuit: CompiledCircuit = ${JSON.stringify(compiled_program)};
return `export const ${name}_circuit: CompiledCircuit = ${JSON.stringify(compiled_program)};
export async function ${name}(${args_with_types}): Promise<${function_signature.returnValue}> {
const program = new Noir(${name}_circuit);
const args: InputMap = { ${args} };
const { returnValue } = await program.execute(args);
return returnValue as ${function_signature.returnValue};
}`;
}
`;
};

export const codegen = (programs: [string, CompiledCircuit][]): string => {
let results = [codegenPrelude];
const primitiveTypeMap = new Map<string, PrimitiveTypesUsed>();
const structTypeMap = new Map<string, { name: string; type: AbiType }[]>();

const functions: string[] = [];
for (const [name, program] of programs) {
const [types_string, function_sig] = generateTsInterface(program.abi, primitiveTypeMap);
functions.push(types_string);
functions.push('\n');
const function_sig = generateTsInterface(program.abi, structTypeMap, primitiveTypeMap);
functions.push(codegenFunction(name, stripUnwantedFields(program), function_sig));
}

Expand All @@ -48,9 +48,11 @@ export const codegen = (programs: [string, CompiledCircuit][]): string => {
primitiveTypeAliases.push(`export type ${value.aliasName} = ${value.tsType};`);
}

results = results.concat(...primitiveTypeAliases, ...functions);
const structTypeDefinitions: string = codegenStructDefinitions(structTypeMap, primitiveTypeMap);

results = results.concat(...primitiveTypeAliases, '', structTypeDefinitions, ...functions);

return results.filter((val) => val !== '').join('\n');
return results.join('\n');
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
70 changes: 34 additions & 36 deletions tooling/noir_codegen/src/noir_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,43 +112,26 @@ function getLastComponentOfPath(str: string): string {
*/
function generateStructInterfaces(
type: AbiType,
output: Set<string>,
structsEncountered: Map<string, { name: string; type: AbiType }[]>,
primitiveTypeMap: Map<string, PrimitiveTypesUsed>,
): string {
let result = '';

) {
// Edge case to handle the array of structs case.
if (type.kind === 'array' && type.type.kind === 'struct' && !output.has(getLastComponentOfPath(type.type.path))) {
result += generateStructInterfaces(type.type, output, primitiveTypeMap);
if (
type.kind === 'array' &&
type.type.kind === 'struct' &&
!structsEncountered.has(getLastComponentOfPath(type.type.path))
) {
generateStructInterfaces(type.type, structsEncountered, primitiveTypeMap);
}
if (type.kind !== 'struct') return result;

// List of structs encountered while viewing this type that we need to generate
// bindings for.
const typesEncountered = new Set<AbiType>();

// Codegen the struct and then its fields, so that the structs fields
// are defined before the struct itself.
let codeGeneratedStruct = '';
let codeGeneratedStructFields = '';
if (type.kind !== 'struct') return;

const structName = getLastComponentOfPath(type.path);
if (!output.has(structName)) {
codeGeneratedStruct += `export type ${structName} = {\n`;
if (!structsEncountered.has(structName)) {
for (const field of type.fields) {
codeGeneratedStruct += ` ${field.name}: ${abiTypeToTs(field.type, primitiveTypeMap)};\n`;
typesEncountered.add(field.type);
}
codeGeneratedStruct += `};`;
output.add(structName);

// Generate code for the encountered structs in the field above
for (const type of typesEncountered) {
codeGeneratedStructFields += generateStructInterfaces(type, output, primitiveTypeMap);
generateStructInterfaces(field.type, structsEncountered, primitiveTypeMap);
}
structsEncountered.set(structName, type.fields);
}

return codeGeneratedStructFields + '\n' + codeGeneratedStruct;
}

/**
Expand All @@ -158,22 +141,37 @@ function generateStructInterfaces(
*/
export function generateTsInterface(
abiObj: Abi,
structsEncountered: Map<string, { name: string; type: AbiType }[]>,
primitiveTypeMap: Map<string, PrimitiveTypesUsed>,
): [string, { inputs: [string, string][]; returnValue: string | null }] {
let result = ``;
const outputStructs = new Set<string>();

): { inputs: [string, string][]; returnValue: string | null } {
// Define structs for composite types
for (const param of abiObj.parameters) {
result += generateStructInterfaces(param.type, outputStructs, primitiveTypeMap);
generateStructInterfaces(param.type, structsEncountered, primitiveTypeMap);
}

// Generating Return type, if it exists
if (abiObj.return_type != null) {
result += generateStructInterfaces(abiObj.return_type.abi_type, outputStructs, primitiveTypeMap);
generateStructInterfaces(abiObj.return_type.abi_type, structsEncountered, primitiveTypeMap);
}

return getTsFunctionSignature(abiObj, primitiveTypeMap);
}

export function codegenStructDefinitions(
structsEncountered: Map<string, { name: string; type: AbiType }[]>,
primitiveTypeMap: Map<string, PrimitiveTypesUsed>,
): string {
let codeGeneratedStruct = '';

for (const [structName, structFields] of structsEncountered) {
codeGeneratedStruct += `export type ${structName} = {\n`;
for (const field of structFields) {
codeGeneratedStruct += ` ${field.name}: ${abiTypeToTs(field.type, primitiveTypeMap)};\n`;
}
codeGeneratedStruct += `};\n\n`;
}

return [result, getTsFunctionSignature(abiObj, primitiveTypeMap)];
return codeGeneratedStruct;
}

function getTsFunctionSignature(
Expand Down
12 changes: 9 additions & 3 deletions tooling/noir_codegen/test/assert_lt/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@ struct MyStruct {
bar: [str<5>; 3],
}

struct NestedStruct {
foo: MyStruct,
bar: [MyStruct; 3],
baz: u64
}

fn main(
x: u64,
y: pub u64,
array: [u8; 5],
my_struct: MyStruct,
my_struct: NestedStruct,
string: str<5>
) -> pub (u64, u64, MyStruct) {
assert(array.len() == 5);
assert(my_struct.foo);
assert(my_struct.foo.foo);
assert(string == "12345");

assert(x < y);
(x + y, 3, my_struct)
(x + y, 3, my_struct.foo)
}
2 changes: 1 addition & 1 deletion tooling/noir_codegen/test/assert_lt/target/assert_lt.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"noir_version":"0.19.4+55670ff82c270534a4bdb999ab0de5cea7017093","hash":11505576107297330043,"backend":"acvm-backend-barretenberg","abi":{"parameters":[{"name":"x","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"private"},{"name":"y","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"},{"name":"array","type":{"kind":"array","length":5,"type":{"kind":"integer","sign":"unsigned","width":8}},"visibility":"private"},{"name":"my_struct","type":{"kind":"struct","path":"MyStruct","fields":[{"name":"foo","type":{"kind":"boolean"}},{"name":"bar","type":{"kind":"array","length":3,"type":{"kind":"string","length":5}}}]},"visibility":"private"},{"name":"string","type":{"kind":"string","length":5},"visibility":"private"}],"param_witnesses":{"array":[{"start":3,"end":8}],"my_struct":[{"start":8,"end":24}],"string":[{"start":24,"end":29}],"x":[{"start":1,"end":2}],"y":[{"start":2,"end":3}]},"return_type":{"abi_type":{"kind":"tuple","fields":[{"kind":"integer","sign":"unsigned","width":64},{"kind":"integer","sign":"unsigned","width":64},{"kind":"struct","path":"MyStruct","fields":[{"name":"foo","type":{"kind":"boolean"}},{"name":"bar","type":{"kind":"array","length":3,"type":{"kind":"string","length":5}}}]}]},"visibility":"public"},"return_witnesses":[31,32,33,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]},"bytecode":"H4sIAAAAAAAA/81XbU/CMBDu5hv4gopvvGw49JOJH1q2wfaN+E+AddFEgzGL/H250Go5dInumnhJ0z2jXJ9er7s+t4yxe7YyZ9lc1Y8N7CK8tWw1A28jvIPwLsJ7Cus5mfIPxquZqBlzmX5DPowiORpIEYoJH6TTJOZRPB0mIhFxEmeDJAxlEiWjdJqOeCqiUIo8TsNcOa7RceQ6DnUUl32EDxA+RPgI4QbCxwifIHyKcBPhM4TPEb5A+BLhK4RbCLcR7iDcRdhjX3mjzUb+jIlyxibPFgFPmYNlVnm2yXjOcps8O3Q8pU2eXTqemU2eHh3PGdQbl22aS8zZYXRn3/07L4FffLN0Mt9mXH3V99iqhuu80GOgzj+wzZxxjGdXjXFLxjg/+Kkb7/T/G8bvVRe/EQxzciqfvgok9QXEp+P4eQHpGT61bRHHw9ahqurrhjCeZfH7JU+OeAqfcM09wn2tEL/SD9x/Pjdl+8yr2do54dVMUJ6Ta0b/3TF92tr3gI53aJNnn3DfuwZHyE8o2FDIQYBr0Q1FFoQmiEsQlCAiociCWASBCKIQhCCIPxB8IPJA2IGYA9EBF3q4LMNcHlsv/E31XGUOyI1g2fpsvfDfqd5T/aQo5MtrERTzYJJlweKpeAzm7/Itf54vPgBYg2KL1RAAAA=="}
{"noir_version":"0.22.0+528d7c9fa244611cd54636493c730e6ce0734ece","hash":9387426530776910287,"abi":{"parameters":[{"name":"x","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"private"},{"name":"y","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"},{"name":"array","type":{"kind":"array","length":5,"type":{"kind":"integer","sign":"unsigned","width":8}},"visibility":"private"},{"name":"my_struct","type":{"kind":"struct","path":"NestedStruct","fields":[{"name":"foo","type":{"kind":"struct","path":"MyStruct","fields":[{"name":"foo","type":{"kind":"boolean"}},{"name":"bar","type":{"kind":"array","length":3,"type":{"kind":"string","length":5}}}]}},{"name":"bar","type":{"kind":"array","length":3,"type":{"kind":"struct","path":"MyStruct","fields":[{"name":"foo","type":{"kind":"boolean"}},{"name":"bar","type":{"kind":"array","length":3,"type":{"kind":"string","length":5}}}]}}},{"name":"baz","type":{"kind":"integer","sign":"unsigned","width":64}}]},"visibility":"private"},{"name":"string","type":{"kind":"string","length":5},"visibility":"private"}],"param_witnesses":{"array":[{"start":3,"end":8}],"my_struct":[{"start":8,"end":73}],"string":[{"start":73,"end":78}],"x":[{"start":1,"end":2}],"y":[{"start":2,"end":3}]},"return_type":{"abi_type":{"kind":"tuple","fields":[{"kind":"integer","sign":"unsigned","width":64},{"kind":"integer","sign":"unsigned","width":64},{"kind":"struct","path":"MyStruct","fields":[{"name":"foo","type":{"kind":"boolean"}},{"name":"bar","type":{"kind":"array","length":3,"type":{"kind":"string","length":5}}}]}]},"visibility":"public"},"return_witnesses":[80,81,82,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]},"bytecode":"H4sIAAAAAAAA/82Y6W/TQBDFNy5HylGgXAEKmHKF2xvbic3VUM62OaDwCYkPSeMIJFAQiui/j59iw+YFIoFnJVaKnF+OyXhmN7vvvVNKfVCTUUofTnZtGuwQL6SPssF7iPcS7yPen3H+myqLj+EVG7ps/JYZ1/fqQZA0aon2dc+rxf0o9IKwX490pMMoHNQi30+iIGrE/bjhxTrwEz0MY3+YBS7L5ejldVikuhwgPkh8iPgw8RLxEeKjxMeIl4mPE58gPkl8ivi0xT5X0hgVC32uKPk+n6G6nCU+R7xCfJ74AvFFYpf4EvEq8WXiK8RXia8RX7fY52oao2qhz1Ul3+cbVJebxLeIbxPfIb5LfI/YI9bENWKfOCAOievEDYt9jtIYkYU+R0q+zzHV5T7xA+KHxI+IHxOvETeJnxCvEz8lfkb8nPgF8UviV2p6/9+g9zeJt4hbxG31ax7lw8Y5oCk0h2zmuSGQZzLEGFjNc1Msz52hzTy35PJMbObZkstzYDPPtlyeO9ANjpodjnDOJSW39p1/z0vzC7+5dbHYZl072bWrJlosnxf5Z6DX1tXsnCkZz/N9xZnzmdIf4iwar+XfXzLeL3rzM8Uwf1wqZicrpPSBpCOX488DSdeImY8F4XrYWlRFY70VrOe8+v1lnh7lqTuC99wV7GuB+s39g/uf1828PnvFxtQ68YoNLblOXiv5/x0zpq2+v5HL27eZ57Zg31tGjpif2LCxkcNIzc1TbLIwDGESwhiEGYhNFqYfjD6YezD0YOLBuINZB4MOphxMLphSMKJgPsFwgskEYwlmkqsmptGqmphDMIRgAsH4gdkD8wRmBwwOmBowMmBewLCASYEFhk0ZBgSKDqMB5gIMBZgIEOUQ0RDOEMsQyBDFEMJrWR0hcnG4gJiFgIVohVCFOIUghXCCKMGBH/Vqq+nDy3L2vEidML8x/7bV9OHlfXZdya698Tj58nXsjkdubzBwdz+NP7qj78m34efR7g+ltqXnYRcAAA=="}
8 changes: 7 additions & 1 deletion tooling/noir_codegen/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ import { expect } from 'chai';
import { assert_lt, MyStruct, u64 } from './codegen/index.js';

it('codegens a callable function', async () => {
const my_struct = { foo: true, bar: ['12345', '12345', '12345'] };

const [sum, constant, struct]: [u64, u64, MyStruct] = await assert_lt(
'2',
'3',
[0, 0, 0, 0, 0],
{ foo: true, bar: ['12345', '12345', '12345'] },
{
foo: my_struct,
bar: [my_struct, my_struct, my_struct],
baz: '64',
},
'12345',
);

Expand Down

0 comments on commit 1dcfcc5

Please sign in to comment.