diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e820381e288..896275461f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -696,6 +696,47 @@ jobs: delete: ${{ !steps.gates_diff.outputs.markdown }} message: ${{ steps.gates_diff.outputs.markdown }} + public-functions-size-report: + needs: [build-images, changes] + if: needs.changes.outputs.non-docs == 'true' && needs.changes.outputs.non-misc-ci == 'true' + runs-on: ${{ github.event.pull_request.user.login || github.actor }}-x86 + permissions: + pull-requests: write + steps: + - uses: actions/checkout@v4 + with: { ref: "${{ env.GIT_COMMIT }}" } + # Only allow one memory-hunger prover test to use this runner + - uses: ./.github/ci-setup-action + with: + concurrency_key: public-functions-size-report-x86 + - name: "Aztec Public Functions Bytecode Size Report" + working-directory: ./noir-projects/ + timeout-minutes: 40 + run: | + earthly-ci \ + --artifact +public-functions-report/public_functions_report.json + mv public_functions_report.json ../public_functions_report.json + + - name: Compare public functions bytecode size reports + id: public_functions_sizes_diff + uses: noir-lang/noir-gates-diff@ef8aaf48fb833f3b6e3f91665bb23afb7e68c6e3 + with: + report: public_functions_report.json + header: | + # Changes to public function bytecode sizes + brillig_report: true + brillig_report_bytes: true + summaryQuantile: 0 # Display any diff in bytecode size count + + - name: Add bytecode size diff to sticky comment + if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: public_functions_size + # delete the comment in case changes no longer impact circuit sizes + delete: ${{ !steps.public_functions_sizes_diff.outputs.markdown }} + message: ${{ steps.public_functions_sizes_diff.outputs.markdown }} + merge-check: runs-on: ubuntu-20.04 needs: diff --git a/noir-projects/Earthfile b/noir-projects/Earthfile index d679b5c72c5..658f5987e48 100644 --- a/noir-projects/Earthfile +++ b/noir-projects/Earthfile @@ -124,3 +124,12 @@ gates-report: RUN ./gates_report.sh SAVE ARTIFACT ./noir-protocol-circuits/gates_report.json gates_report.json + +public-functions-report: + FROM +build-contracts + WORKDIR /usr/src/noir-projects + + RUN cd noir-contracts && node publicFunctionsSizeReport.js ./target/ + + SAVE ARTIFACT ./noir-contracts/public_functions_report.json public_functions_report.json + diff --git a/noir-projects/noir-contracts/publicFunctionsSizeReport.js b/noir-projects/noir-contracts/publicFunctionsSizeReport.js new file mode 100644 index 00000000000..9324690bd24 --- /dev/null +++ b/noir-projects/noir-contracts/publicFunctionsSizeReport.js @@ -0,0 +1,73 @@ +const path = require("path"); +const fs = require("fs"); +const fsp = require("fs").promises; + +// Simple script to extract the exact bytecode size of a contracts public functions. +// The output of this script is meant to be used with the noir-lang/noir-gates-diff project. +// The noir-gates-diff was made for comparing ACIR/Brillig opcodes. +// However, this script was made to re-use the noir-gates-diff for bytecode sizes as +// to minimize both the amount of changes needed in noir-gates-diff and in nargo. +async function main() { + + let [targetDir] = process.argv.slice(2); + if (!targetDir) { + console.log( + "Usage: node extractPublicFunctionsAsNoirArtifacts.js " + ); + return; + } + + let artifactPaths = []; + fs.readdirSync(targetDir).forEach(file => { + // We want to exclude any backups that may be generated by the avm-transpiler + if (path.extname(file) === ".json") { + artifactPaths.push(path.join(targetDir, file)); + } + }); + + let workspaceReport = { + programs: [] + } + for (var i = 0; i < artifactPaths.length; i++) { + let contractArtifactPath = artifactPaths[i]; + const contractArtifact = JSON.parse( + await fsp.readFile(contractArtifactPath, "utf8") + ); + contractArtifact.functions.forEach(async func => { + if (func.custom_attributes.includes("public")) { + if (func.brillig_names.length != 1) { + console.log( + "Expected only a single Brillig function" + ); + return; + } + let func_with_contract_name = contractArtifact.name + "::" + func.brillig_names[0]; + let program_report = { + package_name: "", + functions: [{ name: "main", opcodes: 1 }], + unconstrained_functions: [], + } + // Programs are compared by package name, so we make a unique one for each function here + program_report.package_name = func_with_contract_name; + let bytecode_bytes = Buffer.from(func.bytecode, 'base64'); + let func_report = { + name: "main", + opcodes: bytecode_bytes, + }; + func_report.opcodes = bytecode_bytes.length; + program_report.unconstrained_functions.push(func_report); + workspaceReport.programs.push(program_report); + } + }); + } + + const outPath = path.join("public_functions_report.json"); + + console.log(`Writing to ${outPath}`); + await fsp.writeFile(outPath, JSON.stringify(workspaceReport, null, 2)); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +});