From 2073a091397536c4329906706cde1e5fe0c2c7b6 Mon Sep 17 00:00:00 2001 From: Tom French Date: Mon, 18 Dec 2023 23:46:30 +0000 Subject: [PATCH] feat: add support for codegenning multiple functions which use the same structs in their interface --- tooling/noir_codegen/src/index.ts | 20 +++--- tooling/noir_codegen/src/noir_types.ts | 70 +++++++++---------- .../noir_codegen/test/assert_lt/src/main.nr | 12 +++- .../test/assert_lt/target/assert_lt.json | 2 +- tooling/noir_codegen/test/index.test.ts | 8 ++- 5 files changed, 62 insertions(+), 50 deletions(-) diff --git a/tooling/noir_codegen/src/index.ts b/tooling/noir_codegen/src/index.ts index 8d45b76bd7d..7bef216097a 100644 --- a/tooling/noir_codegen/src/index.ts +++ b/tooling/noir_codegen/src/index.ts @@ -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 = L extends 0 ? never[]: T[] & { length: L }; @@ -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(); + const structTypeMap = new Map(); 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)); } @@ -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 diff --git a/tooling/noir_codegen/src/noir_types.ts b/tooling/noir_codegen/src/noir_types.ts index ba4f8650b3b..0c0e2b7c60f 100644 --- a/tooling/noir_codegen/src/noir_types.ts +++ b/tooling/noir_codegen/src/noir_types.ts @@ -112,43 +112,26 @@ function getLastComponentOfPath(str: string): string { */ function generateStructInterfaces( type: AbiType, - output: Set, + structsEncountered: Map, primitiveTypeMap: Map, -): 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(); - - // 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; } /** @@ -158,22 +141,37 @@ function generateStructInterfaces( */ export function generateTsInterface( abiObj: Abi, + structsEncountered: Map, primitiveTypeMap: Map, -): [string, { inputs: [string, string][]; returnValue: string | null }] { - let result = ``; - const outputStructs = new Set(); - +): { 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, + primitiveTypeMap: Map, +): 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( diff --git a/tooling/noir_codegen/test/assert_lt/src/main.nr b/tooling/noir_codegen/test/assert_lt/src/main.nr index 3b3e04ddece..32d5ff84722 100644 --- a/tooling/noir_codegen/test/assert_lt/src/main.nr +++ b/tooling/noir_codegen/test/assert_lt/src/main.nr @@ -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) } diff --git a/tooling/noir_codegen/test/assert_lt/target/assert_lt.json b/tooling/noir_codegen/test/assert_lt/target/assert_lt.json index a1ab87a99fe..b1865ca5f86 100644 --- a/tooling/noir_codegen/test/assert_lt/target/assert_lt.json +++ b/tooling/noir_codegen/test/assert_lt/target/assert_lt.json @@ -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=="} \ No newline at end of file +{"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=="} \ No newline at end of file diff --git a/tooling/noir_codegen/test/index.test.ts b/tooling/noir_codegen/test/index.test.ts index 48199c13a67..70f3d5bbf89 100644 --- a/tooling/noir_codegen/test/index.test.ts +++ b/tooling/noir_codegen/test/index.test.ts @@ -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', );