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 support for codegenning multiple functions which use the same structs in their interface #3868

Merged
merged 1 commit into from
Dec 19, 2023
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
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
Loading