Skip to content

Commit

Permalink
Inflate as helper in noir wasm and fix browser test
Browse files Browse the repository at this point in the history
  • Loading branch information
spalladino committed Jan 15, 2024
1 parent 9e5e880 commit 08c9b74
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 91 deletions.
1 change: 1 addition & 0 deletions compiler/wasm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"test": "yarn test:build_fixtures && yarn test:node && yarn test:browser",
"test:build_fixtures": "./build-fixtures.sh",
"test:browser": "web-test-runner",
"test:browser:docker": "docker run --rm -v $(cd ../.. && pwd):/usr/src/noir -w /usr/src/noir/compiler/wasm mcr.microsoft.com/playwright:v1.40.0-jammy yarn test:browser",
"test:node": "NODE_NO_WARNINGS=1 mocha --config ./.mocharc.json",
"clean": "rm -rf ./build ./target ./dist public/fixtures/simple/target public/fixtures/with-deps/target",
"nightly:version": "jq --arg new_version \"-$(git rev-parse --short HEAD)$1\" '.version = .version + $new_version' package.json > package-tmp.json && mv package-tmp.json package.json",
Expand Down
3 changes: 2 additions & 1 deletion compiler/wasm/src/index.cts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createNodejsFileManager } from './noir/file-manager/nodejs-file-manager
import { NoirWasmCompiler } from './noir/noir-wasm-compiler';
import { LogData, LogFn } from './utils';
import { CompilationResult } from './types/noir_artifact';
import { inflateDebugSymbols } from './noir/debug';

async function compile(
fileManager: FileManager,
Expand Down Expand Up @@ -46,4 +47,4 @@ async function compile(

const createFileManager = createNodejsFileManager;

export { compile, createFileManager, CompilationResult };
export { compile, createFileManager, inflateDebugSymbols, CompilationResult };
3 changes: 2 additions & 1 deletion compiler/wasm/src/index.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createNodejsFileManager } from './noir/file-manager/nodejs-file-manager
import { NoirWasmCompiler } from './noir/noir-wasm-compiler';
import { LogData, LogFn } from './utils';
import { CompilationResult } from './types/noir_artifact';
import { inflateDebugSymbols } from './noir/debug';

async function compile(
fileManager: FileManager,
Expand Down Expand Up @@ -48,4 +49,4 @@ async function compile(

const createFileManager = createNodejsFileManager;

export { compile, createFileManager, CompilationResult };
export { compile, createFileManager, inflateDebugSymbols, CompilationResult };
6 changes: 6 additions & 0 deletions compiler/wasm/src/noir/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { inflate } from 'pako';

/** Decompresses and decodes the debug symbols */
export function inflateDebugSymbols(debugSymbols: string) {
return JSON.parse(inflate(Buffer.from(debugSymbols, 'base64'), { to: 'string', raw: true }));
}
39 changes: 19 additions & 20 deletions compiler/wasm/test/compiler/browser/compile_with_deps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getPaths } from '../../shared';
import { expect } from '@esm-bundle/chai';
import { compile, createFileManager } from '@noir-lang/noir_wasm';
import { CompiledContract } from '../../../src/types/noir_artifact';
import { shouldCompileIdentically } from '../shared/compile_with_deps.test';

const paths = getPaths('.');

Expand All @@ -21,24 +22,22 @@ async function getPrecompiledSource(path: string): Promise<any> {
return JSON.parse(compiledData);
}

describe('noir-compiler', () => {
it('both nargo and noir_wasm should compile identically', async () => {
const { contractExpectedArtifact } = paths;
const fm = createFileManager('/');
const files = Object.values(paths).filter((fileOrDir) => /^\.?\/.*\..*$/.test(fileOrDir));
for (const path of files) {
console.log(path);
await fm.writeFile(path, (await getFile(path)).body as ReadableStream<Uint8Array>);
}
const nargoArtifact = (await getPrecompiledSource(contractExpectedArtifact)) as CompiledContract;
nargoArtifact.functions.sort((a, b) => a.name.localeCompare(b.name));
const noirWasmArtifact = await compile(fm, '/fixtures/noir-contract');
if (!('contract' in noirWasmArtifact)) {
throw new Error('Compilation failed');
}
const noirWasmContract = noirWasmArtifact.contract;
expect(noirWasmContract).not.to.be.undefined;
noirWasmContract.functions.sort((a, b) => a.name.localeCompare(b.name));
expect(nargoArtifact).to.deep.eq(noirWasmContract);
}).timeout(60 * 20e3);
describe('noir-compiler/browser', () => {
shouldCompileIdentically(
async () => {
const { contractExpectedArtifact } = paths;
const fm = createFileManager('/');
const files = Object.values(paths).filter((fileOrDir) => /^\.?\/.*\..*$/.test(fileOrDir));
for (const path of files) {
console.log(path);
await fm.writeFile(path, (await getFile(path)).body as ReadableStream<Uint8Array>);
}
const nargoArtifact = (await getPrecompiledSource(contractExpectedArtifact)) as CompiledContract;
const noirWasmArtifact = await compile(fm, '/fixtures/noir-contract');

return { nargoArtifact, noirWasmArtifact };
},
expect,
60 * 20e3,
);
});
76 changes: 7 additions & 69 deletions compiler/wasm/test/compiler/node/compile_with_deps.test.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,20 @@
import { join, resolve } from 'path';
import { getPaths } from '../../shared';

import { inflate } from 'pako';
import { expect } from 'chai';
import { readFile } from 'fs/promises';
import { compile, createFileManager } from '@noir-lang/noir_wasm';
import { CompiledContract, DebugFileMap, DebugInfo } from '../../../src/types/noir_artifact';
import { ContractCompilationArtifacts, NoirFunctionEntry } from 'dist/types/src/types/noir_artifact';
import { readFile } from 'fs/promises';
import { CompiledContract } from '../../../src/types/noir_artifact';
import { shouldCompileIdentically } from '../shared/compile_with_deps.test';

const basePath = resolve(join(__dirname, '../../'));
const { contractProjectPath, contractExpectedArtifact } = getPaths(basePath);

describe('noir-compiler', () => {
it('both nargo and noir_wasm should compile identically', async () => {
describe('noir-compiler/node', () => {
shouldCompileIdentically(async () => {
const fm = createFileManager(contractProjectPath);
const nargoArtifact = JSON.parse((await readFile(contractExpectedArtifact)).toString()) as CompiledContract;
const [nargoDebugInfos, nargoFileMap] = deleteDebugMetadata(nargoArtifact);
normalizeVersion(nargoArtifact);

const noirWasmArtifact = await compile(fm);
const noirWasmContract = (noirWasmArtifact as ContractCompilationArtifacts).contract;
expect(noirWasmContract).not.to.be.undefined;
const [noirWasmDebugInfos, norWasmFileMap] = deleteDebugMetadata(noirWasmContract);
normalizeVersion(noirWasmContract);

// We first compare both contracts without considering debug info
expect(nargoArtifact).to.deep.eq(noirWasmContract);

// Compare the file maps, ignoring keys, since those depend in the order in which files are visited,
// which may change depending on the file manager implementation. Also ignores paths, since the base
// path is reported differently between nargo and noir-wasm.
expect(getSources(nargoFileMap)).to.have.members(getSources(norWasmFileMap));

// Compare the debug symbol information, ignoring the actual ids used for file identifiers.
// Debug symbol info looks like the following, what we need is to ignore the 'file' identifiers
// {"locations":{"0":[{"span":{"start":141,"end":156},"file":39},{"span":{"start":38,"end":76},"file":38},{"span":{"start":824,"end":862},"file":23}]}}
expect(nargoDebugInfos).to.deep.eq(noirWasmDebugInfos);
}).timeout(5000);
return { nargoArtifact, noirWasmArtifact };
}, expect);
});

/** Remove commit identifier from version, which may not match depending on cached nargo and noir-wasm */
function normalizeVersion(contract: CompiledContract) {
contract.noir_version = contract.noir_version.replace(/\+.+$/, '');
}

/** Decompresses and decodes the debug symbols */
function inflateDebugSymbols(debugSymbols: string) {
return JSON.parse(inflate(Buffer.from(debugSymbols, 'base64'), { to: 'string', raw: true }));
}

/** Extracts the debug symbols from all functions, decodes them, removes their file identifiers, and deletes them from the artifact. */
function extractDebugInfos(fns: NoirFunctionEntry[]) {
return fns.map((fn) => {
const debugSymbols = inflateDebugSymbols(fn.debug_symbols);
delete (fn as Partial<NoirFunctionEntry>).debug_symbols;
clearFileIdentifiers(debugSymbols);
return debugSymbols;
});
}

/** Deletes all debug info from a contract and returns it. */
function deleteDebugMetadata(contract: CompiledContract) {
contract.functions.sort((a, b) => a.name.localeCompare(b.name));
const fileMap = contract.file_map;
delete (contract as Partial<CompiledContract>).file_map;
return [extractDebugInfos(contract.functions), fileMap];
}

/** Clears file identifiers from a set of debug symbols. */
function clearFileIdentifiers(debugSymbols: DebugInfo) {
for (const loc of Object.values(debugSymbols.locations)) {
for (const span of loc) {
span.file = 0;
}
}
}

/** Returns list of sources from file map, dropping paths along the way, since they don't match depending on the file manager. */
function getSources(fileMap: DebugFileMap) {
return Object.values(fileMap).map((file) => file.source);
}
80 changes: 80 additions & 0 deletions compiler/wasm/test/compiler/shared/compile_with_deps.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { CompilationResult, inflateDebugSymbols } from '@noir-lang/noir_wasm';
import { type expect as Expect } from 'chai';
import {
CompiledContract,
ContractCompilationArtifacts,
DebugFileMap,
DebugInfo,
NoirFunctionEntry,
} from '../../../src/types/noir_artifact';

export function shouldCompileIdentically(
compileFn: () => Promise<{ nargoArtifact: CompiledContract; noirWasmArtifact: CompilationResult }>,
expect: typeof Expect,
timeout = 5000,
) {
it('both nargo and noir_wasm should compile identically', async () => {
// Compile!
const { nargoArtifact, noirWasmArtifact } = await compileFn();

// Prepare nargo artifact
const [nargoDebugInfos, nargoFileMap] = deleteDebugMetadata(nargoArtifact);
normalizeVersion(nargoArtifact);

// Prepare noir-wasm artifact
const noirWasmContract = (noirWasmArtifact as ContractCompilationArtifacts).contract;
expect(noirWasmContract).not.to.be.undefined;
const [noirWasmDebugInfos, norWasmFileMap] = deleteDebugMetadata(noirWasmContract);
normalizeVersion(noirWasmContract);

// We first compare both contracts without considering debug info
expect(nargoArtifact).to.deep.eq(noirWasmContract);

// Compare the file maps, ignoring keys, since those depend in the order in which files are visited,
// which may change depending on the file manager implementation. Also ignores paths, since the base
// path is reported differently between nargo and noir-wasm.
expect(getSources(nargoFileMap)).to.have.members(getSources(norWasmFileMap));

// Compare the debug symbol information, ignoring the actual ids used for file identifiers.
// Debug symbol info looks like the following, what we need is to ignore the 'file' identifiers
// {"locations":{"0":[{"span":{"start":141,"end":156},"file":39},{"span":{"start":38,"end":76},"file":38},{"span":{"start":824,"end":862},"file":23}]}}
expect(nargoDebugInfos).to.deep.eq(noirWasmDebugInfos);
}).timeout(timeout);
}

/** Remove commit identifier from version, which may not match depending on cached nargo and noir-wasm */
function normalizeVersion(contract: CompiledContract) {
contract.noir_version = contract.noir_version.replace(/\+.+$/, '');
}

/** Extracts the debug symbols from all functions, decodes them, removes their file identifiers, and deletes them from the artifact. */
function extractDebugInfos(fns: NoirFunctionEntry[]) {
return fns.map((fn) => {
const debugSymbols = inflateDebugSymbols(fn.debug_symbols);
delete (fn as Partial<NoirFunctionEntry>).debug_symbols;
clearFileIdentifiers(debugSymbols);
return debugSymbols;
});
}

/** Deletes all debug info from a contract and returns it. */
function deleteDebugMetadata(contract: CompiledContract) {
contract.functions.sort((a, b) => a.name.localeCompare(b.name));
const fileMap = contract.file_map;
delete (contract as Partial<CompiledContract>).file_map;
return [extractDebugInfos(contract.functions), fileMap];
}

/** Clears file identifiers from a set of debug symbols. */
function clearFileIdentifiers(debugSymbols: DebugInfo) {
for (const loc of Object.values(debugSymbols.locations)) {
for (const span of loc) {
span.file = 0;
}
}
}

/** Returns list of sources from file map, dropping paths along the way, since they don't match depending on the file manager. */
function getSources(fileMap: DebugFileMap) {
return Object.values(fileMap).map((file) => file.source);
}

0 comments on commit 08c9b74

Please sign in to comment.