C generation

This commit is contained in:
Jordi Baylina
2019-11-23 19:12:58 +01:00
parent 51ff27b9c6
commit 66291a0efe
31 changed files with 3295 additions and 239 deletions

52
src/buildwasm.js Normal file
View File

@@ -0,0 +1,52 @@
const ModuleBuilder = require("wasmbuilder").ModuleBuilder;
const gen = require("./gencode");
module.exports = function buildWasm(ctx) {
const fDefined = {};
ctx.module = new ModuleBuilder();
for (let f in ctx.functions) {
ctx.f = ctx.module.addFunction(f);
ctx.c = ctx.f.getCodeBuilder();
ctx.scope = {};
for (let p in ctx.functions[f].params) {
const param = ctx.functions[f].params[p];
ctx.f.addParam(param.name, "i32");
ctx.scope[param.name] = {
type: "PARAM",
sels: param.sels,
getter: () => { return ctx.c.getLocal(param.name); },
setter: (v) => { return ctx.c.setLocal(param.name, v); }
};
}
gen(ctx, ctx.functions[f].block);
}
for (let i=0; i<ctx.components.length; i++) {
const h = hashComponentCall(ctx, i);
const fName = ctx.components[i].temlate+"_"+h;
if (!fDefined[fName]) {
ctx.f = ctx.module.addFunction(fName);
ctx.c = ctx.f.getCodeBuilder();
ctx.scope = {};
for (let p in ctx.components[i].params) {
ctx.scope[p] = createConstant(ctx, ctx.components[i].params[p]);
}
gen(ctx, ctx.templates[ctx.components[i].temlate].block);
}
ctx.components[i].f = fName;
}
};
function buildSetSignal(ctx) {
}

377
src/c_build.js Normal file
View File

@@ -0,0 +1,377 @@
/*
Copyright 2018 0KIMS association.
This file is part of circom (Zero Knowledge Circuit Compiler).
circom is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
circom is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with circom. If not, see <https://www.gnu.org/licenses/>.
*/
const utils = require("./utils");
const assert = require("assert");
const gen = require("./c_gen");
module.exports = buildC;
function buildC(ctx) {
ctx.code = "";
ctx.tmpNames = {};
ctx.getTmpName = getTmpName;
ctx.codes_sizes = [];
ctx.definedSizes = {};
ctx.addSizes = addSizes;
const entryTables = buildEntryTables(ctx);
const code = buildCode(ctx);
const compnentsArray = buildComponentsArray(ctx);
const headder = buildHeader(ctx);
const sizes = buildSizes(ctx);
const mapIsInput = buildMapIsInput(ctx);
const wit2Sig = buildWit2Sig(ctx);
const circuitVar = buildCircuitVar(ctx);
return "" +
headder + "\n" +
sizes + "\n" +
entryTables + "\n" +
code + "\n" +
compnentsArray + "\n" +
mapIsInput + "\n" +
wit2Sig +"\n" +
circuitVar;
}
function buildEntryTables(ctx) {
const codes_hashMaps = [];
const codes_componentEntries = [];
const definedHashTables = {};
for (let i=0; i<ctx.components.length; i++) {
const {htName, htMap} = addHashTable(i);
let code = "";
const componentEntriesTableName = ctx.getTmpName("entryTable_" + ctx.components[i].template);
code += `Circom_ComponentEntry ${componentEntriesTableName}[${htMap.length}] = {\n`;
for (let j=0; j<htMap.length; j++) {
const entry = ctx.components[i].names.o[htMap[j]];
code += j>0 ? " ," : " ";
const sizeName = ctx.addSizes(entry.sizes);
const ty = entry.type == "S" ? "_typeSignal" : "_typeComponent";
code += `{${entry.offset},${sizeName}, ${ty}}\n`;
}
code += "};\n";
codes_componentEntries.push(code);
ctx.components[i].htName = htName;
ctx.components[i].etName = componentEntriesTableName;
}
return "" +
"// HashMaps\n" +
codes_hashMaps.join("\n") + "\n" +
"\n" +
"// Component Entries\n" +
codes_componentEntries.join("\n") + "\n" +
"\n";
function addHashTable(cIdx) {
const keys = Object.keys(ctx.components[cIdx].names.o);
assert(keys.length<128);
keys.sort((a,b) => ((a>b) ? 1 : -1));
const h = utils.fnvHash(keys.join(","));
if (definedHashTables[h]) return definedHashTables[h];
definedHashTables[h] = {};
definedHashTables[h].htName = ctx.getTmpName("ht_"+ctx.components[cIdx].template);
definedHashTables[h].htMap = [];
const t = [];
for (let i=0; i<keys.length; i++) {
definedHashTables[h].htMap[i] = keys[i];
const h2 = utils.fnvHash(keys[i]);
let pos = parseInt(h2.slice(-2), 16);
while (t[pos]) pos = (pos + 1) % 256;
t[pos] = [h2, i];
}
let code = `Circom_HashEntry ${definedHashTables[h].htName}[256] = {`;
for (let i=0; i<256; i++) {
code += i>0 ? "," : "";
if (t[i]) {
code += `{0x${t[i][0]}LL, ${t[i][1]}} /* ${keys[t[i][1]]} */`;
} else {
code += "{0,0}";
}
}
code += "};\n";
codes_hashMaps.push(code);
return definedHashTables[h];
}
}
function buildCode(ctx) {
const globalNames = ctx.tmpNames;
const fDefined = {};
const functions = [];
for (let f in ctx.functions) {
ctx.scope = {};
const paramsList = [];
for (let p in ctx.functions[f].params) {
const param = ctx.functions[f].params[p];
paramsList.push("POINTER "+param.name);
ctx.scope[param.name] = {
type: "LOCAL",
sels: param.sels,
getter: () => { return param.name; },
};
}
ctx.code += "void "+f+"(POINTER _ret, "+paramsList.join(",")+") {\n";
ctx.code += gen(ctx, ctx.functions[f].block);
ctx.code += "}";
}
for (let i=0; i<ctx.components.length; i++) {
const h = hashComponentCall(ctx, i);
const fName = ctx.components[i].template+"_"+h;
if (!fDefined[fName]) {
const scope = {_prefix : ""};
ctx.scopes = [scope];
ctx.nScopes = 0;
ctx.code = "";
ctx.codeHeader = "// Header\n";
ctx.codeFooter = "// Footer\n";
ctx.tmpNames = Object.assign({},globalNames);
for (let p in ctx.components[i].params) {
newRef(ctx, "BIGINT", p, ctx.components[i].params[p]);
}
gen(ctx, ctx.templates[ctx.components[i].template].block);
const S = `void ${fName}(Circom_CalcWit *ctx) {\n` +
utils.ident(
ctx.codeHeader + "\n" +
ctx.code + "\n" +
ctx.codeFooter
) +
"}\n";
functions.push(S);
}
ctx.components[i].fnName = fName;
}
return functions.join("\n");
}
function buildComponentsArray(ctx) {
const ccodes = [];
ccodes.push(`Circom_Component _components[${ctx.components.length}] = {\n`);
for (let i=0; i< ctx.components.length; i++) {
ccodes.push(i>0 ? " ," : " ");
ccodes.push(`{${ctx.components[i].htName},${ctx.components[i].etName},${ctx.components[i].fnName}, ${ctx.components[i].nInSignals}}\n`);
}
ccodes.push("};\n");
const codeComponents = ccodes.join("");
return "" +
"// Components\n" +
codeComponents +
"\n";
}
function buildHeader(ctx) {
return "#include \"circom.h\"\n" +
"#include \"calcwit.h\"\n" +
`#define NSignals ${ctx.signals.length}\n` +
`#define NComponents ${ctx.components.length}\n` +
`#define NInputs ${ctx.components[ ctx.getComponentIdx("main") ].nInSignals}\n`+
`#define NOutputs ${ctx.totals[ ctx.stOUTPUT ]}\n`+
`#define NVars ${ctx.totals[ctx.stONE] + ctx.totals[ctx.stOUTPUT] + ctx.totals[ctx.stPUBINPUT] + ctx.totals[ctx.stPRVINPUT] + ctx.totals[ctx.stINTERNAL]}\n` +
"\n";
}
function buildSizes(ctx) {
return "// Sizes\n" +
ctx.codes_sizes.join("\n");
}
function buildMapIsInput(ctx) {
const arr = [];
let line = "";
let acc = 0;
let i;
for (i=0; i<ctx.signals.length; i++) {
if (ctx.signals[i].o & ctx.IN) {
acc = acc | (1 << (i%32) );
}
if ((i+1)%32==0) {
line += (i>31) ? "," : " ";
line += toHex(acc);
acc = 0;
if ( i % (32*64) == 0) {
arr.push(line);
line = "";
}
}
}
if ((i%32) != 0) {
line += (i>31) ? "," : " ";
line += toHex(acc);
}
if (line != "") {
arr.push(line);
}
return "// mapIsArray\n" +
`u32 _mapIsInput[${Math.floor((ctx.signals.length-1) / 32)+1}] = {\n`+
arr.join("\n") + "\n" +
"};\n";
function toHex(number) {
if (number < 0) number = 0xFFFFFFFF + number + 1;
let S=number.toString(16).toUpperCase();
while (S.length<8) S = "0" + S;
return "0x"+S;
}
}
function buildWit2Sig(ctx) {
const codes = [];
const NVars =
ctx.totals[ctx.stONE] +
ctx.totals[ctx.stOUTPUT] +
ctx.totals[ctx.stPUBINPUT] +
ctx.totals[ctx.stPRVINPUT] +
ctx.totals[ctx.stINTERNAL];
const arr = Array(NVars);
for (let i=0; i<ctx.signals.length; i++) {
const outIdx = ctx.signals[i].id;
if (typeof outIdx == "undefined") continue;
if (ctx.signals[i].e>=0) continue; // If has an alias, continue..
assert(outIdx<NVars);
if (typeof arr[ctx.signals[i].id] == "undefined") {
arr[outIdx] = i;
}
}
codes.push("// Signal Table\n");
codes.push(`int _wit2sig[${NVars}] = {\n`);
let code = "";
for (let i=0; i<NVars; i++) {
code += (i>0) ? ",": " ";
code += arr[i];
if ((i>0)&&(i%64 == 0)) {
if (code != "") codes.push(code + "\n");
codes.push(code);
code =0;
}
}
if (code != "") codes.push(code + "\n");
codes.push("};\n");
return codes.join("");
}
function buildCircuitVar() {
return "Circom_Circuit _circuit = {\n" +
" NSignals,\n"+
" NComponents,\n"+
" NInputs,\n"+
" NOutputs,\n"+
" NVars,\n"+
" _wit2sig,\n"+
" _components,\n"+
" _mapIsInput\n"+
"};\n";
}
function hashComponentCall(ctx, cIdx) {
// TODO: At the moment generate a diferent function for each instance of the component
return cIdx;
}
function getTmpName(_suggestedName) {
let suggestedName;
if (_suggestedName) {
suggestedName = trimUnderscore(_suggestedName);
} else {
suggestedName = "tmp";
}
if (typeof(this.tmpNames[suggestedName]) == "undefined") {
this.tmpNames[suggestedName] = 1;
return "_"+suggestedName;
} else {
const name = "_" + suggestedName + "_" + this.tmpNames[suggestedName];
this.tmpNames[suggestedName]++;
return name;
}
function trimUnderscore(str) {
let p1=0;
while ((p1 < str.length)&&(str[p1] == "_")) p1++;
let p2=str.length;
while ((p2 > 0)&&(str[p2-1] == "_")) p2--;
return str.slice(p1,p2);
}
}
function addSizes(_sizes) {
const sizes = _sizes || [];
let name = "sizes";
for (let i=0; i<sizes.length;i++) {
name+="_"+sizes[i];
}
if (name=="sizes") name="sizes_0";
if (this.definedSizes[name]) return this.definedSizes[name];
const labelName = this.getTmpName(name);
this.definedSizes[name] = labelName;
const accSizes = utils.accSizes(sizes);
let code = `Circom_Size ${labelName}[${accSizes.length}] = {`;
for (let i=0; i<accSizes.length; i++) {
if (i>0) code += ",";
code += accSizes[i];
}
code += "};\n";
this.codes_sizes.push(code);
return labelName;
}

941
src/c_gen.js Normal file
View File

@@ -0,0 +1,941 @@
const bigInt = require("big-integer");
const utils = require("./utils");
module.exports = gen;
function newRef(ctx, type, _name, value, _sizes) {
const isValue = ((typeof(value) != "undefined")&&(value != null));
let name;
let sizes;
if (!_name) {
name = ctx.getTmpName();
} else {
if (_name[0] =="_") {
name = ctx.getTmpName(_name);
} else {
name = _name;
}
}
if (typeof(_sizes) == "string") {
sizes = _sizes;
} else if (Array.isArray(_sizes)) {
sizes = newSizes(ctx, _sizes);
} else if (isValue) {
sizes = newSizes(ctx, utils.extractSizes(value));
} else {
sizes = newSizes(ctx, []);
}
const scope = ctx.scopes[ctx.scopes.length-1];
if (scope[name]) return error("Variable already exists: " + name);
const label = scope._prefix + name;
scope[name] = {
stack: true,
type: type,
used: false,
sizes: sizes,
label: label
};
if (isValue) {
scope[name].value = value;
}
return name;
}
function newSizes(ctx, sizes) {
const scope = ctx.scopes[ctx.scopes.length-1];
const name = ctx.getTmpName("_sz");
scope[name] = {
stack: true,
type: "SIZES",
used: false,
dim: sizes.length,
label: name,
value: sizes
};
return name;
}
function instantiateRef(ctx, name) {
const v = getScope(ctx, name);
if (!v.stack) return error("Using a non existing var: " + name);
if (v.used) return;
if (v.type=="BIGINT") {
const iSize = getScope(ctx, v.sizes);
if (iSize.used) {
const labelSize = iSize.label;
ctx.codeHeader += `PBigInt ${v.label};\n`;
ctx.code += `${v.label} = ctx->allocBigInts(${labelSize});\n`;
ctx.codeFooter += `ctx->freeBigInts(${v.label}, ${labelSize});\n`;
} else if (iSize.value) {
const labelSize = ctx.addSizes(iSize.value);
ctx.codeHeader += `PBigInt ${v.label} = ctx->allocBigInts(${labelSize});\n`;
ctx.codeFooter += `ctx->freeBigInts(${v.label}, ${labelSize});\n`;
} else {
return error(ctx, null, "Undefined Sizes: " +name);
}
} else if (v.type=="INT") {
ctx.codeHeader += `int ${v.label};\n`;
} else if (v.type=="SIZES") {
ctx.codeHeader += `Circom_Sizes ${v.label};\n`;
}
v.used = true;
}
function error(ctx, ast, errStr) {
ctx.error = {
pos: {
first_line: ast.first_line,
first_column: ast.first_column,
last_line: ast.last_line,
last_column: ast.last_column
},
errStr: errStr,
ast: ast,
message: errStr
};
}
function getScope(ctx, name) {
for (let i=ctx.scopes.length-1; i>=0; i--) {
if (ctx.scopes[i][name]) return ctx.scopes[i][name];
}
return null;
}
function gen(ctx, ast) {
if ((ast.type == "NUMBER") ) {
return genNumber(ctx, ast);
} else if (ast.type == "VARIABLE") {
return genVariable(ctx, ast);
} else if (ast.type == "PIN") {
return genPin(ctx, ast);
} else if (ast.type == "OP") {
if (ast.op == "=") {
return genVarAssignement(ctx, ast);
} else if (ast.op == "<--") {
return genVarAssignement(ctx, ast);
} else if (ast.op == "<==") {
return genSignalAssignConstrain(ctx, ast);
} else if (ast.op == "===") {
return genConstraint(ctx, ast);
} else if (ast.op == "+=") {
return genVarAddAssignement(ctx, ast);
} else if (ast.op == "*=") {
return genVarMulAssignement(ctx, ast);
} else if (ast.op == "+") {
return genAdd(ctx, ast);
} else if (ast.op == "-") {
return genSub(ctx, ast);
} else if (ast.op == "UMINUS") {
return genUMinus(ctx, ast);
} else if (ast.op == "*") {
return genMul(ctx, ast);
} else if (ast.op == "%") {
return genMod(ctx, ast);
} else if (ast.op == "PLUSPLUSRIGHT") {
return genPlusPlusRight(ctx, ast);
} else if (ast.op == "PLUSPLUSLEFT") {
return genPlusPlusLeft(ctx, ast);
} else if (ast.op == "MINUSMINUSRIGHT") {
return genMinusMinusRight(ctx, ast);
} else if (ast.op == "MINUSMINUSLEFT") {
return genMinusMinusLeft(ctx, ast);
} else if (ast.op == "**") {
return genExp(ctx, ast);
} else if (ast.op == "/") {
return genDiv(ctx, ast);
} else if (ast.op == "\\") {
return genIDiv(ctx, ast);
} else if (ast.op == "&") {
return genBAnd(ctx, ast);
} else if (ast.op == "&&") {
return genAnd(ctx, ast);
} else if (ast.op == "||") {
return genOr(ctx, ast);
} else if (ast.op == "<<") {
return genShl(ctx, ast);
} else if (ast.op == ">>") {
return genShr(ctx, ast);
} else if (ast.op == "<") {
return genLt(ctx, ast);
} else if (ast.op == ">") {
return genGt(ctx, ast);
} else if (ast.op == "<=") {
return genLte(ctx, ast);
} else if (ast.op == ">=") {
return genGte(ctx, ast);
} else if (ast.op == "==") {
return genEq(ctx, ast);
} else if (ast.op == "!=") {
return genNeq(ctx, ast);
} else if (ast.op == "?") {
return genTerCon(ctx, ast);
} else {
error(ctx, ast, "Invalid operation: " + ast.op);
}
} else if (ast.type == "DECLARE") {
if (ast.declareType == "COMPONENT") {
return genDeclareComponent(ctx, ast);
} else if ((ast.declareType == "SIGNALIN")||
(ast.declareType == "SIGNALOUT")||
(ast.declareType == "SIGNAL")) {
return genDeclareSignal(ctx, ast);
} else if (ast.declareType == "VARIABLE") {
return genDeclareVariable(ctx, ast);
} else {
error(ctx, ast, "Invalid declaration: " + ast.declareType);
}
} else if (ast.type == "FUNCTIONCALL") {
return genFunctionCall(ctx, ast);
} else if (ast.type == "BLOCK") {
return genBlock(ctx, ast);
} else if (ast.type == "COMPUTE") {
return genCompute(ctx, ast);
} else if (ast.type == "FOR") {
return genFor(ctx, ast);
} else if (ast.type == "WHILE") {
return genWhile(ctx, ast);
} else if (ast.type == "IF") {
return genIf(ctx, ast);
} else if (ast.type == "RETURN") {
return genReturn(ctx, ast);
} else if (ast.type == "INCLUDE") {
return genInclude(ctx, ast);
} else if (ast.type == "ARRAY") {
return genArray(ctx, ast);
} else {
error(ctx, ast, "GEN -> Invalid AST node type: " + ast.type);
}
}
function genBlock(ctx, ast) {
const oldCode = ctx.code;
let res = null;
ctx.code = "";
for (let i=0; i<ast.statements.length; i++) {
if (["BLOCK", "COMPUTE", "FOR", "WHILE", "IF"].indexOf(ast.statements[i].type)<0) {
genSrcComment(ctx, ast.statements[i]);
}
res = gen(ctx, ast.statements[i]);
if (ctx.error) return;
}
if (ctx.scopes.length>1) {
ctx.code = oldCode + `{\n${utils.ident(ctx.code)}}\n`;
} else {
ctx.code = oldCode + ctx.code;
}
return res;
}
function genSrcComment(ctx, ast) {
let codes = [];
const fl= ast.first_line-1;
const ll = ast.last_line-1;
const fc = ast.first_column-1;
const lc = ast.last_column;
for (let i=fl; i<=ll; i++) {
const p1 = i==fl ? fc : 0;
const p2 = i==ll ? lc : -1;
codes.push(ctx.includedFiles[ctx.fileName][i].slice(p1, p2>=0 ? p2 : undefined));
}
ctx.code += "\n/* "+codes.join("\n")+"*/\n";
}
function genDeclareComponent(ctx, ast) {
const scope = ctx.scopes[ctx.scopes.length - 1];
if (ast.name.type != "VARIABLE") return error(ctx, ast, "Invalid component name");
if (getScope(ctx, ast.name.name)) return error(ctx, ast, "Name already exists: "+ast.name.name);
scope[ast.name.name] = {
type: "COMPONENT",
label: ast.name.name
};
return ast.name.name;
}
function genDeclareSignal(ctx, ast) {
const scope = ctx.scopes[ctx.scopes.length-1];
if (ast.name.type != "VARIABLE") return error(ctx, ast, "Invalid component name");
if (getScope(ctx, ast.name.name)) return error(ctx, ast, "Name already exists: "+ast.name.name);
const res = {
type: "SIGNAL",
label: ast.name.name
};
scope[ast.name.name] = res;
return res;
}
function genDeclareVariable(ctx, ast) {
const scope = ctx.scopes[ctx.scopes.length-1];
const varName = ast.name.name;
const labelName = scope._prefix + ast.name.name;
if (ast.name.type != "VARIABLE") return error(ctx, ast, "Invalid component name");
if (ctx.scope[varName]) return error(ctx, ast, "Name already exists: "+varName);
const sizes=[];
let instantiate = false;
for (let i=0; i< ast.name.selectors.length; i++) {
const size = gen(ctx, ast.name.selectors[i]);
if (ctx.error) return;
if (size.used) {
instantiate = true;
sizes.push(`BigInt2Int(${scope[size].label})`);
} else {
sizes.push(size.value.toJSNumber());
}
sizes.push( size.value.toJSNumber() );
}
const vSizes = newRef(ctx, "SIZES", "_sizes");
const iSizes = scope[vSizes];
iSizes.dim = ast.name.selectors.length;
if (instantiate) {
instantiateRef(ctx, vSizes);
ctx.code += `${iSizes.label}[${iSizes.dim+1}]=0`;
ctx.code += `${iSizes.label}[${iSizes.dim}]=1`;
for (let i=iSizes.dim-1; i>=0; i--) {
ctx.code += `${iSizes.label}[${i}] = ${sizes[i]}*${iSizes.label}[${i+1}];\n`;
}
} else {
iSizes.value = sizes;
}
const res = ctx.scope[varName] = {
stack: true,
type: "BIGINT",
sizes: vSizes,
label: labelName
};
scope[varName] = res;
return res;
}
function genNumber(ctx, ast) {
return newRef(ctx, "BIGINT", "_num", ast.value);
}
function genGetOffset(ctx, vOffset, vSizes, sels) {
let rN = 0;
let rStr = "";
let iOffset;
if (vOffset) {
iOffset = getScope(ctx, vOffset);
if (iOffset.used) {
rStr += iOffset.label;
} else {
rN += iOffset.value.toJSNumber();
}
}
if ((sels)&&(sels.length>0)) {
const iSizes = getScope(ctx, vSizes);
for (let i=0; i<sels.length; i++) {
const vIdx = gen(ctx, sels[i]);
const iIdx = getScope(ctx, vIdx);
if ((!iIdx.used) && (!iSizes.used)) {
rN = rN + iIdx.value * iSizes.sizes[i+1];
} else {
if (rN>0) {
if (rStr != "") rStr += " + ";
rStr += rN;
rN =0;
}
if (rStr != "") rStr += " + ";
if (iIdx.used) {
rStr += vIdx;
} else {
rStr += iIdx.value;
}
rStr += "*";
if (iSizes.used) {
rStr += `${iSizes.label}[${i+1}]`;
} else {
rStr += iSizes.sizes[i+1];
}
}
}
}
if (rStr == "") {
const o = newRef(ctx, "INT", "_offset", rN);
return o;
} else {
if (rN>0) {
if (rStr != "") rStr += " + ";
rStr += rN;
rN =0;
}
if (rStr == iOffset.label) {
return vOffset;
} else {
const res = newRef(ctx, "INT", "_offset");
instantiateRef(ctx, res);
ctx.code += `${res} = ${rStr};\n`;
return res;
}
}
}
function genVariable(ctx, ast) {
const v = getScope(ctx, ast.name);
if (v.type == "SIGNAL") {
let vOffset;
if (ast.selectors.length>0) {
const vsOffset = genGetSigalOffset(ctx, "ctx->cIdx", ast.name);
const vsSizes = genGetSignalSizes(ctx, "ctx->cIdx", ast.name);
vOffset = genGetOffset(ctx, vsOffset, vsSizes, ast.selectors );
} else {
vOffset = genGetSigalOffset(ctx, "ctx->cIdx", ast.name);
}
return genGetSignal(ctx, "ctx->cIdx", vOffset);
} else if (v.type == "BIGINT") {
const vOffset = genGetOffset(ctx, 0, v.sizes, ast.sels );
if (v.used) {
if (vOffset == 0) {
return ast.name;
} else {
const res = newRef(ctx, "BIGINT", "_v");
instantiateRef(ctx, res);
ctx.code += `${res} = ${ast.name} + ${vOffset};\n`;
return res;
}
} else {
if (typeof(vOffset) == "string") {
instantiateRef(ctx, ast.name);
const vConstant = instantiateConstant(ctx, v.value);
const res = newRef(ctx, "BIGINT", "_v");
instantiateRef(ctx, res);
ctx.code += `${res} = ${vConstant} + ${vOffset};\n`;
return res;
} else {
const sa = utils.subArray(v.value, ast.selectors);
const res = newRef(ctx, "BIGINT", "_v", sa);
return res;
}
}
}
const sels = [];
for (let i=0; i<ast.selectors.length; i++) {
sels.push(gen(ctx, ast.selectors[i]));
if (ctx.error) return;
}
if (!v) {
return error(ctx, ast, "Invalid left operand");
}
if (v.type == "VARIABLE") {
return `ctx.getVar("${ast.name}",[${sels.join(",")}])`;
} else if (v.type == "SIGNAL") {
return `ctx.getSignal("${ast.name}", [${sels.join(",")}])`;
} else {
error(ctx, ast, "Invalid Variable type");
}
}
function instantiateConstant(ctx, value) {
const flatedValue = utils.flatArray(value);
const res = ctx.getTmpName("_const");
ctx.codeHeader += `PBigInt ${res};\n`;
ctx.code += `${res.label} = ctx->allocBigInts(${flatedValue.length});\n`;
for (let i=0; i<flatedValue.length; i++) {
ctx.code += `mpz_init_set_str(${res.label}[${i}], ${flatedValue[i].toString(16)}, 16);\n`;
}
ctx.codeFooter += `ctx->freeBigInts(${res.label});\n`;
return res;
}
function genPin(ctx, ast) {
let vcIdx;
if (ast.component.selectors.length>0) {
const vcOffset = genGetSubComponentOffset(ctx, "ctx->cIdx", ast.component.name);
const vcSizes = genGetSubComponentSizes(ctx, "ctx->cIdx", ast.component.name);
vcIdx = genGetOffset(ctx, vcOffset, vcSizes, ast.component.selectors );
} else {
vcIdx = genGetSubComponentOffset(ctx, "ctx->cIdx", ast.component.name);
}
let vsIdx;
if (ast.pin.selectors.length>0) {
const vsOffset = genGetSigalOffset(ctx, vcIdx, ast.pin.name);
const vsSizes = genGetSignalSizes(ctx, vcIdx, ast.pin.name);
vsIdx = genGetOffset(ctx, vsOffset, vsSizes, ast.pin.selectors );
} else {
vsIdx = genGetSigalOffset(ctx, vcIdx, ast.pin.name);
}
return genGetSignal(ctx, vcIdx, vsIdx);
}
function genGetSubComponentOffset(ctx, cIdx, label) {
const vOffset = newRef(ctx, "INT", "_compIdx");
instantiateRef(ctx, vOffset);
const h = utils.fnvHash(label);
ctx.code += `${vOffset} = ctx->getSubComponentOffset(${cIdx}, 0x${h}LL /* ${label} */);\n`;
return vOffset;
}
function genGetSubComponentSizes(ctx, cIdx, label) {
const vSizes = newRef(ctx, "SIZES", "_compSizes");
instantiateRef(ctx, vSizes);
const h = utils.fnvHash(label);
ctx.code += `${vSizes} = ctx->getSubComponentSizes(${cIdx}, 0x${h}LL /* ${label} */);\n`;
return vSizes;
}
function genGetSigalOffset(ctx, sIdx, label) {
const vOffset = newRef(ctx, "INT", "_sigIdx");
instantiateRef(ctx, vOffset);
const h = utils.fnvHash(label);
ctx.code += `${vOffset} = ctx->getSignalOffset(${sIdx}, 0x${h}LL /* ${label} */);\n`;
return vOffset;
}
function genGetSignalSizes(ctx, sIdx, label) {
const vSizes = newRef(ctx, "SIZES", "_sigSizes");
instantiateRef(ctx, vSizes);
const h = utils.fnvHash(label);
ctx.code += `${vSizes} = ctx->getSignalSizes(${sIdx}, 0x${h}LL /* ${label} */);\n`;
return vSizes;
}
function genSetSignal(ctx, cIdx, sIdx, value) {
ctx.code += `ctx->setSignal(${cIdx}, ${sIdx}, ${value});\n`;
return value;
}
function genGetSignal(ctx, cIdx, sIdx) {
const res = newRef(ctx, "BIGINT", "_sigValue");
instantiateRef(ctx, res);
ctx.code += `ctx->getSignal(${cIdx}, ${sIdx}, ${res});\n`;
return res;
}
function genVarAssignement(ctx, ast) {
let lName;
let sels;
if (ctx.error) return;
if (ast.values[0].type == "PIN") {
let vcIdx;
if (ast.values[0].component.selectors.length>0) {
const vcOffset = genGetSubComponentOffset(ctx, "ctx->cIdx", ast.values[0].component.name);
const vcSizes = genGetSubComponentSizes(ctx, "ctx->cIdx", ast.values[0].component.name);
vcIdx = genGetOffset(ctx, vcOffset, vcSizes, ast.values[0].component.selectors );
} else {
vcIdx = genGetSubComponentOffset(ctx, "ctx->cIdx", ast.values[0].component.name);
}
let vsIdx;
if (ast.values[0].pin.selectors.length>0) {
const vsOffset = genGetSigalOffset(ctx, vcIdx, ast.values[0].pin.name);
const vsSizes = genGetSignalSizes(ctx, vcIdx, ast.values[0].pin.name);
vsIdx = genGetOffset(ctx, vsOffset, vsSizes, ast.values[0].pin.selectors );
} else {
vsIdx = genGetSigalOffset(ctx, vcIdx, ast.values[0].pin.name);
}
const vVal = gen(ctx, ast.values[1]);
genSetSignal(ctx, vcIdx, vsIdx, vVal);
return vVal;
}
if (ast.values[0].type == "DECLARE") {
lName = gen(ctx, ast.values[0]);
if (ctx.error) return;
sels = [];
} else {
lName = ast.values[0].name;
sels = ast.values[0].selectors;
}
const left = getScope(ctx, lName);
if (!left) return error(ctx, ast, "Variable does not exists: "+ast.values[0].name);
// Component instantiation is already done.
if (left.type == "COMPONENT") {
ctx.last = lName;
return;
}
const rName = gen(ctx, ast.values[1]);
const right = getScope(ctx, rName);
if (left.type == "SIGNAL") {
let vsIdx;
if (sels.length>0) {
const vsOffset = genGetSigalOffset(ctx, "ctx->cIdx", lName);
const vsSizes = genGetSignalSizes(ctx, "ctx->cIdx", lName);
vsIdx = genGetOffset(ctx, vsOffset, vsSizes, sels );
} else {
vsIdx = genGetSigalOffset(ctx, "ctx->cIdx", lName);
}
return genSetSignal(ctx, "ctx->cIdx", vsIdx, rName);
} else if (left.type == "VAR") {
if (left.used) {
if (!right.used) {
instantiateRef(ctx, rName);
}
ctx.code += `BigInt_copy(${left.label}, ${right.label});\n`;
return lName;
} else {
if (right.used) {
instantiateRef(ctx, lName);
ctx.code += `BigInt_copy(${left.label}, ${right.label});\n`;
return lName;
} else {
if (!left.value) {
left.value = right.value;
} else {
if (!left.value.equals(right.value)) {
if (ctx.scopes.length > 1) {
instantiateRef(ctx, lName);
ctx.code += `BigInt_copy(${left.label}, ${right.label});\n`;
} else {
left.value = right.value;
}
}
}
}
}
} else {
return error(ctx, ast, "Assigning to invalid");
}
}
function genConstraint(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
const strErr = ast.fileName + ":" + ast.first_line + ":" + ast.first_column;
ctx.code += `ctx->checkConstraint(${a}, ${b}, "${strErr}");`;
}
function genArray(ctx, ast) {
let S = "[";
for (let i=0; i<ast.values.length; i++) {
if (i>0) S += ",";
S += gen(ctx, ast.values[i]);
}
S+="]";
return S;
}
function genFunctionCall(ctx, ast) {
let S = "[";
for (let i=0; i<ast.params.length; i++) {
if (i>0) S += ",";
S += gen(ctx, ast.params[i]);
}
S+="]";
return `ctx.callFunction("${ast.name}", ${S})`;
}
function genFor(ctx, ast) {
ctx.scopes.push({});
const init = gen(ctx, ast.init);
if (ctx.error) return;
const condition = gen(ctx, ast.condition);
if (ctx.error) return;
const step = gen(ctx, ast.step);
if (ctx.error) return;
const body = gen(ctx, ast.body);
if (ctx.error) return;
ctx.scopes.pop();
return `for (${init};bigInt(${condition}).neq(bigInt(0));${step}) { \n${body}\n }\n`;
}
function genWhile(ctx, ast) {
const condition = gen(ctx, ast.condition);
if (ctx.error) return;
const body = gen(ctx, ast.body);
if (ctx.error) return;
return `while (bigInt(${condition}).neq(bigInt(0))) {\n${body}\n}\n`;
}
function genCompute(ctx, ast) {
const body = gen(ctx, ast.body);
if (ctx.error) return;
return `{\n${body}\n}\n`;
}
function genIf(ctx, ast) {
const condition = gen(ctx, ast.condition);
if (ctx.error) return;
const thenBody = gen(ctx, ast.then);
if (ctx.error) return;
if (ast.else) {
const elseBody = gen(ctx, ast.else);
if (ctx.error) return;
return `if (bigInt(${condition}).neq(bigInt(0))) {\n${thenBody}\n} else {\n${elseBody}\n}\n`;
} else {
return `if (bigInt(${condition}).neq(bigInt(0))) {\n${thenBody}\n}\n`;
}
}
function genReturn(ctx, ast) {
const value = gen(ctx, ast.value);
if (ctx.error) return;
return `return ${value};`;
}
function genSignalAssignConstrain(ctx, ast) {
const res = genVarAssignement(ctx, ast);
genConstraint(ctx, ast);
return res;
// return genVarAssignement(ctx, ast);
}
function genVarAddAssignement(ctx, ast) {
return genVarAssignement(ctx, {values: [ast.values[0], {type: "OP", op: "+", values: ast.values}]});
}
function genVarMulAssignement(ctx, ast) {
return genVarAssignement(ctx, {values: [ast.values[0], {type: "OP", op: "*", values: ast.values}]});
}
function genPlusPlusRight(ctx, ast) {
return `(${genVarAssignement(ctx, {values: [ast.values[0], {type: "OP", op: "+", values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}]}]})}).add(__P__).sub(bigInt(1)).mod(__P__)`;
}
function genPlusPlusLeft(ctx, ast) {
return genVarAssignement(ctx, {values: [ast.values[0], {type: "OP", op: "+", values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}]}]});
}
function genMinusMinusRight(ctx, ast) {
return `(${genVarAssignement(ctx, {values: [ast.values[0], {type: "OP", op: "-", values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}]}]})}).add(__P__).sub(bigInt(1)).mod(__P__)`;
}
function genMinusMinusLeft(ctx, ast) {
return genVarAssignement(ctx, {values: [ast.values[0], {type: "OP", op: "-", values: [ast.values[0], {type: "NUMBER", value: bigInt(1)}]}]});
}
function genAdd(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${a}).add(bigInt(${b})).mod(__P__)`;
}
function genMul(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${a}).mul(bigInt(${b})).mod(__P__)`;
}
function genSub(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${a}).add(__P__).sub(bigInt(${b})).mod(__P__)`;
}
function genDiv(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${a}).mul( bigInt(${b}).inverse(__P__) ).mod(__P__)`;
}
function genIDiv(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${a}).div( bigInt(${b}))`;
}
function genExp(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${a}).modPow(bigInt(${b}), __P__)`;
}
function genBAnd(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${a}).and(bigInt(${b})).and(__MASK__)`;
}
function genAnd(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `((bigInt(${a}).neq(bigInt(0)) && bigInt(${b}).neq(bigInt(0))) ? bigInt(1) : bigInt(0))`;
}
function genOr(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `((bigInt(${a}).neq(bigInt(0)) || bigInt(${b}).neq(bigInt(0))) ? bigInt(1) : bigInt(0))`;
}
function genShl(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${b}).greater(bigInt(256)) ? 0 : bigInt(${a}).shl(bigInt(${b})).and(__MASK__)`;
}
function genShr(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${b}).greater(bigInt(256)) ? 0 : bigInt(${a}).shr(bigInt(${b})).and(__MASK__)`;
}
function genMod(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${a}).mod(bigInt(${b}))`;
}
function genLt(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${a}).lt(bigInt(${b})) ? 1 : 0`;
}
function genGt(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${a}).gt(bigInt(${b})) ? 1 : 0`;
}
function genLte(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${a}).lesserOrEquals(bigInt(${b})) ? 1 : 0`;
}
function genGte(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `bigInt(${a}).greaterOrEquals(bigInt(${b})) ? 1 : 0`;
}
function genEq(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `(bigInt(${a}).eq(bigInt(${b})) ? 1 : 0)`;
}
function genNeq(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
return `(bigInt(${a}).eq(bigInt(${b})) ? 0 : 1)`;
}
function genUMinus(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
return `__P__.sub(bigInt(${a})).mod(__P__)`;
}
function genTerCon(ctx, ast) {
const a = gen(ctx, ast.values[0]);
if (ctx.error) return;
const b = gen(ctx, ast.values[1]);
if (ctx.error) return;
const c = gen(ctx, ast.values[2]);
if (ctx.error) return;
return `bigInt(${a}).neq(bigInt(0)) ? (${b}) : (${c})`;
}
function genInclude(ctx, ast) {
return ast.block ? gen(ctx, ast.block) : "";
}

119
src/c_tester.js Normal file
View File

@@ -0,0 +1,119 @@
const chai = require("chai");
const assert = chai.assert;
const fs = require("fs");
var tmp = require("tmp-promise");
const path = require("path");
const compiler = require("./compiler");
const util = require("util");
const exec = util.promisify(require("child_process").exec);
const stringifyBigInts = require("snarkjs").stringifyBigInts;
const unstringifyBigInts = require("snarkjs").unstringifyBigInts;
const bigInt = require("snarkjs").bigInt;
module.exports = c_tester;
async function c_tester(circomFile, mainComponent, _options) {
tmp.setGracefulCleanup();
mainComponent = mainComponent || "main";
const dir = await tmp.dir({prefix: "circom_", unsafeCleanup: true });
const baseName = path.basename(circomFile, ".circom");
const options = Object.assign({}, _options);
options.cSourceWriteStream = fs.createWriteStream(path.join(dir.path, baseName + ".cpp"));
options.symWriteStream = fs.createWriteStream(path.join(dir.path, baseName + ".sym"));
options.mainComponent = mainComponent;
await compiler(circomFile, options);
const cdir = path.join(__dirname, "..", "c");
await exec("g++" +
` ${path.join(dir.path, baseName + ".cpp")} ` +
` ${path.join(cdir, "main.cpp")}` +
` ${path.join(cdir, "calcwit.cpp")}` +
` ${path.join(cdir, "utils.cpp")}` +
` -o ${path.join(dir.path, baseName)}` +
` -I ${cdir}` +
" -lgmp -std=c++11 -DSANITY_CHECK"
);
console.log(dir.path);
return new CTester(dir, baseName, mainComponent);
}
class CTester {
constructor(dir, baseName, mainComponent) {
this.dir=dir;
this.baseName = baseName;
this.mainComponent = mainComponent;
}
async release() {
await this.dir.cleanup();
}
async calculateWitness(input) {
await fs.promises.writeFile(
path.join(this.dir.path, "in.json"),
JSON.stringify(stringifyBigInts(input), null, 1)
);
await exec(`${path.join(this.dir.path, this.baseName)}` +
` ${path.join(this.dir.path, "in.json")}` +
` ${path.join(this.dir.path, "out.json")}`
);
const resStr = await fs.promises.readFile(
path.join(this.dir.path, "out.json")
);
const res = unstringifyBigInts(JSON.parse(resStr));
return res;
}
async _loadSymbols() {
this.symbols = {};
const symsStr = await fs.promises.readFile(
path.join(this.dir.path, this.baseName + ".sym"),
"utf8"
);
const lines = symsStr.split("\n");
for (let i=0; i<lines.length; i++) {
const arr = lines[i].split(",");
if (arr.length!=3) continue;
this.symbols[arr[2]] = {
idx: Number(arr[0]),
idxWit: Number(arr[1])
};
}
}
async assertOut(actualOut, expectedOut) {
const self = this;
if (!self.symbols) await self._loadSymbols();
checkObject("main", expectedOut);
function checkObject(prefix, eOut) {
if (Array.isArray(eOut)) {
for (let i=0; i<eOut.length; i++) {
checkObject(prefix + "["+i+"]", eOut[i]);
}
} else if (typeof eOut == "object") {
for (let k in eOut) {
checkObject(prefix + "."+k, eOut[k]);
}
} else {
const ba = bigInt(actualOut[self.symbols[prefix].idxWit]).toString();
const be = bigInt(eOut).toString();
assert.strictEqual(ba, be, prefix);
}
}
}
}

View File

@@ -22,10 +22,12 @@ const path = require("path");
const bigInt = require("big-integer");
const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
const __MASK__ = new bigInt(2).pow(253).minus(1);
const sONE = 0;
const assert = require("assert");
const gen = require("./gencode");
const buildC = require("./c_build");
const exec = require("./exec");
const lc = require("./lcalgebra");
const Ctx = require("./ctx");
module.exports = compile;
@@ -48,41 +50,34 @@ async function compile(srcFile, options) {
assert(ast.type == "BLOCK");
const ctx = {
scopes: [{}],
signals: {
one: {
fullName: "one",
value: bigInt(1),
equivalence: "",
direction: ""
}
},
currentComponent: "",
constraints: [],
components: {},
templates: {},
functions: {},
functionParams: {},
filePath: fullFilePath,
fileName: fullFileName,
verbose: options.verbose || false
};
const ctx = new Ctx();
ctx.mainComponent = options.mainComponent || "main";
ctx.filePath= fullFilePath;
ctx.fileName= fullFileName;
ctx.includedFiles = {};
ctx.includedFiles[fullFileName] = src.split("\n");
ctx.verbose= options.verbose || false;
exec(ctx, ast);
if (!ctx.components["main"]) {
if (ctx.error) {
throw(ctx.error);
}
if (ctx.getComponentIdx(ctx.mainComponent)<0) {
throw new Error("A main component must be defined");
}
if (ctx.verbose) console.log("Classify Signals");
classifySignals(ctx);
if (ctx.verbose) console.log("Reduce Constraints");
if (ctx.verbose) console.log("Reduce Constants");
reduceConstants(ctx);
if (options.reduceConstraints) {
if (ctx.verbose) console.log("Reduce Constraints");
// Repeat while reductions are performed
let oldNConstrains = -1;
while (ctx.constraints.length != oldNConstrains) {
@@ -97,121 +92,134 @@ async function compile(srcFile, options) {
throw(ctx.error);
}
ctx.scopes = [{}];
const mainCode = gen(ctx,ast);
if (options.cSourceWriteStream) {
const cSrc = buildC(ctx);
options.cSourceWriteStream.write(cSrc);
}
// const mainCode = gen(ctx,ast);
if (ctx.error) throw(ctx.error);
const def = buildCircuitDef(ctx, mainCode);
if (options.r1csWriteStream) {
buildR1cs(ctx, options.r1csWriteStream);
}
if (options.symWriteStream) {
buildSyms(ctx, options.symWriteStream);
}
// const def = buildCircuitDef(ctx, mainCode);
return def;
}
function classifySignals(ctx) {
const ERROR = 0xFFFF;
function priorize(t1, t2) {
if ((t1 == "error") || (t2=="error")) return "error";
if (t1 == "internal") {
if ((t1 == ERROR) || (t2==ERROR)) return ERROR;
if (t1 == ctx.stINTERNAL) {
return t2;
} else if (t2=="internal") {
} else if (t2==ctx.stINTERNAL) {
return t1;
}
if ((t1 == "one") || (t2 == "one")) return "one";
if ((t1 == "constant") || (t2 == "constant")) return "constant";
if (t1!=t2) return "error";
if ((t1 == ctx.stONE) || (t2 == ctx.stONE)) return ctx.stONE;
if ((t1 == ctx.stCONSTANT) || (t2 == ctx.stCONSTANT)) return ctx.stCONSTANT;
if ((t1 == ctx.stDISCARDED) || (t2 == ctx.stDISCARDED)) return ctx.stDISCARDED;
if (t1!=t2) return ERROR;
return t1;
}
// First classify the signals
for (let s in ctx.signals) {
const signal = ctx.signals[s];
let tAll = "internal";
let tAll = ctx.stINTERNAL;
let lSignal = signal;
let end = false;
while (!end) {
let t = lSignal.category || "internal";
if (s == "one") {
t = "one";
} else if (lSignal.value) {
t = "constant";
} else if (lSignal.component=="main") {
if (lSignal.direction == "IN") {
if (lSignal.private) {
t = "prvInput";
let t = lSignal.c || ctx.stINTERNAL;
if (s == 0) {
t = ctx.stONE;
} else if (lSignal.v) {
t = ctx.stCONSTANT;
} else if (lSignal.o & ctx.MAIN) {
if (lSignal.o & ctx.IN) {
if (lSignal.o & ctx.PRV) {
t = ctx.stPRVINPUT;
} else {
t = "pubInput";
t = ctx.stPUBINPUT;
}
} else if (lSignal.direction == "OUT") {
t = "output";
} else if (lSignal.o & ctx.OUT) {
t = ctx.stOUTPUT;
}
}
tAll = priorize(t,tAll);
if (lSignal.equivalence) {
lSignal = ctx.signals[lSignal.equivalence];
if (lSignal.e>=0) {
lSignal = ctx.signals[lSignal.e];
} else {
end=true;
}
}
if (tAll == "error") {
if (tAll == ERROR) {
throw new Error("Incompatible types in signal: " + s);
}
lSignal.category = tAll;
lSignal.c = tAll;
}
}
function generateWitnessNames(ctx) {
const totals = {
"output": 0,
"pubInput": 0,
"one": 0,
"prvInput": 0,
"internal": 0,
"constant": 0,
};
const totals = {};
totals[ctx.stONE] = 0;
totals[ctx.stOUTPUT] = 0;
totals[ctx.stPUBINPUT] = 0;
totals[ctx.stPRVINPUT] = 0;
totals[ctx.stINTERNAL] = 0;
totals[ctx.stDISCARDED] = 0;
totals[ctx.stCONSTANT] = 0;
const ids = {};
const counted = {};
// First classify the signals
for (let s in ctx.signals) {
for (let s=0; s<ctx.signals.length; s++) {
if ((ctx.verbose)&&(s%10000 == 0)) console.log("generate witness (counting): ", s);
const signal = ctx.signals[s];
let lSignal = signal;
while (lSignal.equivalence) lSignal = ctx.signals[lSignal.equivalence];
while (lSignal.e>=0) lSignal = ctx.signals[lSignal.e];
if (!counted[lSignal.fullName]) {
counted[lSignal.fullName] = true;
totals[lSignal.category] ++;
if (!( lSignal.o & ctx.COUNTED) ) {
lSignal.o |= ctx.COUNTED;
totals[lSignal.c] ++;
}
}
ids["one"] = 0;
ids["output"] = 1;
ids["pubInput"] = ids["output"] + totals["output"];
ids["prvInput"] = ids["pubInput"] + totals["pubInput"];
ids["internal"] = ids["prvInput"] + totals["prvInput"];
ids["constant"] = ids["internal"] + totals["internal"];
const nSignals = ids["constant"] + totals["constant"];
ids[ctx.stONE] = 0;
ids[ctx.stOUTPUT] = 1;
ids[ctx.stPUBINPUT] = ids[ctx.stOUTPUT] + totals[ctx.stOUTPUT];
ids[ctx.stPRVINPUT] = ids[ctx.stPUBINPUT] + totals[ctx.stPUBINPUT];
ids[ctx.stINTERNAL] = ids[ctx.stPRVINPUT] + totals[ctx.stPRVINPUT];
ids[ctx.stDISCARDED] = ids[ctx.stINTERNAL] + totals[ctx.stINTERNAL];
ids[ctx.stCONSTANT] = ids[ctx.stDISCARDED] + totals[ctx.stDISCARDED];
const nSignals = ids[ctx.stCONSTANT] + totals[ctx.stCONSTANT];
ctx.signalNames = new Array(nSignals);
for (let i=0; i< nSignals; i++) ctx.signalNames[i] = [];
ctx.signalName2Idx = {};
for (let s=0; s<ctx.signals.length; s++) {
if ((ctx.verbose)&&(s%10000 == 0)) console.log("seting id: ", s);
for (let s in ctx.signals) {
const signal = ctx.signals[s];
let lSignal = signal;
while (lSignal.equivalence) {
lSignal = ctx.signals[lSignal.equivalence];
while (lSignal.e>=0) {
lSignal = ctx.signals[lSignal.e];
}
if ( typeof(lSignal.id) === "undefined" ) {
lSignal.id = ids[lSignal.category] ++;
lSignal.id = ids[lSignal.c] ++;
}
signal.id = lSignal.id;
ctx.signalNames[signal.id].push(signal.fullName);
ctx.signalName2Idx[signal.fullName] = signal.id;
}
ctx.totals = totals;
@@ -225,6 +233,7 @@ function reduceConstants(ctx) {
if (!lc.isZero(c)) {
newConstraints.push(c);
}
delete ctx.constraints[i];
}
ctx.constraints = newConstraints;
}
@@ -232,10 +241,12 @@ function reduceConstants(ctx) {
function reduceConstrains(ctx) {
indexVariables();
let possibleConstraints = Object.keys(ctx.constraints);
let ii=0;
while (possibleConstraints.length>0) {
let nextPossibleConstraints = {};
for (let i in possibleConstraints) {
if ((ctx.verbose)&&(i%10000 == 0)) console.log("reducing constraints: ", i);
ii++;
if ((ctx.verbose)&&(ii%10000 == 0)) console.log("reducing constraints: ", i);
if (!ctx.constraints[i]) continue;
const c = ctx.constraints[i];
@@ -248,13 +259,13 @@ function reduceConstrains(ctx) {
// Mov to C if possible.
if (isConstant(c.a)) {
const ct = {type: "NUMBER", value: c.a.values["one"]};
const ct = {type: "NUMBER", value: c.a.values[sONE]};
c.c = lc.add(lc.mul(c.b, ct), c.c);
c.a = { type: "LINEARCOMBINATION", values: {} };
c.b = { type: "LINEARCOMBINATION", values: {} };
}
if (isConstant(c.b)) {
const ct = {type: "NUMBER", value: c.b.values["one"]};
const ct = {type: "NUMBER", value: c.b.values[sONE]};
c.c = lc.add(lc.mul(c.a, ct), c.c);
c.a = { type: "LINEARCOMBINATION", values: {} };
c.b = { type: "LINEARCOMBINATION", values: {} };
@@ -265,8 +276,8 @@ function reduceConstrains(ctx) {
if (isolatedSignal) {
let lSignal = ctx.signals[isolatedSignal];
while (lSignal.equivalence) {
lSignal = ctx.signals[lSignal.equivalence];
while (lSignal.e>=0) {
lSignal = ctx.signals[lSignal.e];
}
@@ -296,7 +307,7 @@ function reduceConstrains(ctx) {
ctx.constraints[i] = null;
lSignal.category = "constant";
lSignal.c = ctx.stDISCARDED;
} else {
if (lc.isZero(c.c)) ctx.constraints[i] = null;
}
@@ -332,8 +343,8 @@ function reduceConstrains(ctx) {
function unindexVariables() {
for (let s in ctx.signals) {
let lSignal = ctx.signals[s];
while (lSignal.equivalence) {
lSignal = ctx.signals[lSignal.equivalence];
while (lSignal.e>=0) {
lSignal = ctx.signals[lSignal.e];
}
if (lSignal.inConstraints) delete lSignal.inConstraints;
}
@@ -342,8 +353,8 @@ function reduceConstrains(ctx) {
/*
function unlinkSignal(signalName, cidx) {
let lSignal = ctx.signals[signalName];
while (lSignal.equivalence) {
lSignal = ctx.signals[lSignal.equivalence];
while (lSignal.e>=0) {
lSignal = ctx.signals[lSignal.e];
}
if ((lSignal.inConstraints)&&(lSignal.inConstraints[cidx])) {
delete lSignal.inConstraints[cidx];
@@ -353,8 +364,8 @@ function reduceConstrains(ctx) {
function linkSignal(signalName, cidx) {
let lSignal = ctx.signals[signalName];
while (lSignal.equivalence) {
lSignal = ctx.signals[lSignal.equivalence];
while (lSignal.e>=0) {
lSignal = ctx.signals[lSignal.e];
}
if (!lSignal.inConstraints) lSignal.inConstraints = {};
lSignal.inConstraints[cidx] = true;
@@ -363,21 +374,22 @@ function reduceConstrains(ctx) {
function getFirstInternalSignal(ctx, l) {
for (let k in l.values) {
const signal = ctx.signals[k];
if (signal.category == "internal") return k;
if (signal.c == ctx.stINTERNAL) return k;
}
return null;
}
function isConstant(l) {
for (let k in l.values) {
if ((k != "one") && (!l.values[k].isZero())) return false;
if ((k != sONE) && (!l.values[k].isZero())) return false;
}
if (!l.values["one"] || l.values["one"].isZero()) return false;
if (!l.values[sONE] || l.values[sONE].isZero()) return false;
return true;
}
}
/*
function buildCircuitDef(ctx, mainCode) {
const res = {
@@ -436,6 +448,9 @@ function buildCircuitDef(ctx, mainCode) {
return res;
}
*/
/*
Build constraints
@@ -485,5 +500,96 @@ function buildConstraints(ctx) {
return res;
}
function buildR1cs(ctx, strm) {
strm.write(Buffer.from([0x72,0x31,0x63,0x73]));
writeU32(1);
writeU32(4);
writeU32(1 + ctx.totals.output + ctx.totals.pubInput + ctx.totals.prvInput + ctx.totals.internal);
writeU32(ctx.totals.output);
writeU32(ctx.totals.pubInput);
writeU32(ctx.totals.prvInput);
writeU32(ctx.constraints.length);
for (let i=0; i<ctx.constraints.length; i++) {
if ((ctx.verbose)&&(i%10000 == 0)) console.log("writing constraint: ", i);
writeConstraint(ctx.constraints[i]);
}
function writeU32(v) {
const b = Buffer.allocUnsafe(4);
b.writeInt32LE(v);
strm.write(b);
}
function writeConstraint(c) {
writeLC(c.a);
writeLC(c.b);
writeLC(lc.negate(c.c));
}
function writeLC(lc) {
const idxs = Object.keys(lc.values);
writeU32(idxs.length);
for (let s in lc.values) {
let lSignal = ctx.signals[s];
while (lSignal.e >=0 ) lSignal = ctx.signals[lSignal.e];
writeU32(lSignal.id);
writeBigInt(lc.values[s]);
}
}
function writeBigInt(n) {
const bytes = [];
let r = bigInt(n);
while (r.greater(bigInt.zero)) {
bytes.push(r.and(bigInt("255")).toJSNumber());
r = r.shiftRight(8);
}
assert(bytes.length<=32);
assert(bytes.length>0);
strm.write( Buffer.from([bytes.length, ...bytes ]));
}
}
function buildSyms(ctx, strm) {
addSymbolsComponent(ctx.mainComponent + ".", ctx.getComponentIdx(ctx.mainComponent));
function addSymbolsComponent(prefix, idComponet) {
for (let n in ctx.components[idComponet].names.o) {
const entrie = ctx.components[idComponet].names.o[n];
addSymbolArray(prefix+n, entrie.type, entrie.sizes, entrie.offset);
}
}
function addSymbolArray(prefix, type, sizes, offset) {
if (sizes.length==0) {
if (type == "S") {
let s=offset;
while (ctx.signals[s].e >= 0) s = ctx.signals[s].e;
let wId = ctx.signals[s].id;
if (typeof(wId) == "undefined") wId=-1;
strm.write(`${offset},${wId},${prefix}\n`);
} else {
addSymbolsComponent(prefix+".", offset);
}
return 1;
} else {
let acc = 0;
for (let i=0; i<sizes[0]; i++) {
acc += addSymbolArray(`${prefix}[${i}]`, type, sizes.slice(1), offset + acc );
}
return acc;
}
}
}

171
src/ctx.js Normal file
View File

@@ -0,0 +1,171 @@
const bigInt = require("big-integer");
class TableName {
constructor (ctx) {
this.ctx = ctx;
this.o = {};
}
_allocElement(name, _sizes, type) {
const sizes = _sizes || [];
let l = 1;
for (let i=0; i<sizes.length; i++) {
l = l*sizes[i];
}
this.o[name] = {
sizes: sizes,
type: type
};
return l;
}
addSignal(name, sizes) {
const l = this._allocElement(name, sizes, "S");
const o = this.ctx.nSignals;
this.o[name].offset = o;
this.ctx.nSignals += l;
if (l>1) {
return [o, o+l];
} else {
return o;
}
}
addComponent(name, sizes) {
const l = this._allocElement(name, sizes, "C");
const o = this.ctx.nComponents;
this.o[name].offset = o;
this.ctx.nComponents += l;
if (l>1) {
return [o, o+l];
} else {
return o;
}
}
_getElement(name, _sels, type) {
const sels = _sels || [];
const s = this.o[name];
if (!s) return -1;
if (s.type != type) return -1;
if (sels.length > s.sizes.length) return -1;
let l=1;
for (let i = s.sizes.length-1; i>sels.length; i--) {
l = l*s.sizes[i];
}
let o =0;
let p=1;
for (let i=sels.length-1; i>=0; i--) {
if (sels[i] > s.sizes[i]) return -1; // Out of range
if (sels[i] < 0) return -1; // Out of range
o += p*sels[i];
p *= s.sizes[i];
}
if (l>1) {
return [s.offset + o, s.offset + o + l];
} else {
return s.offset + o;
}
}
getSignalIdx(name, sels) {
return this._getElement(name, sels, "S");
}
getComponentIdx(name, sels) {
return this._getElement(name, sels, "C");
}
getSizes(name) {
return this.o[name].sels;
}
}
module.exports = class Ctx {
constructor() {
this.stONE = 1;
this.stOUTPUT = 2;
this.stPUBINPUT = 3;
this.stPRVINPUT = 4;
this.stINTERNAL = 5;
this.stDISCARDED = 6;
this.stCONSTANT = 7;
this.IN = 0x01;
this.OUT = 0x02;
this.PRV = 0x04;
this.ONE = 0x08;
this.MAIN = 0x10;
this.COUNTED = 0x20;
this.scopes = [{}];
this.signals = [];
this.currentComponent= -1;
this.constraints= [];
this.components= [];
this.templates= {};
this.functions= {};
this.functionParams= {};
this.nSignals = 0;
this.nComponents =0;
this.names = new TableName(this);
this.main=false;
const oneIdx = this.addSignal("one");
this.signals[oneIdx] = {
v: bigInt(1),
o: this.ONE,
e: -1,
};
}
addSignal(name, sizes) {
if (this.currentComponent>=0) {
return this.components[this.currentComponent].names.addSignal(name, sizes);
} else {
return this.names.addSignal(name, sizes);
}
}
addComponent(name, sizes) {
if (this.currentComponent>=0) {
return this.components[this.currentComponent].names.addComponent(name, sizes);
} else {
return this.names.addComponent(name, sizes);
}
}
getSignalIdx(name, sels) {
if (this.currentComponent>=0) {
return this.components[this.currentComponent].names.getSignalIdx(name, sels);
} else {
return this.names.getSignalIdx(name, sels);
}
}
getComponentIdx(name, sels) {
if (this.currentComponent>=0) {
return this.components[this.currentComponent].names.getComponentIdx(name, sels);
} else {
return this.names.getComponentIdx(name, sels);
}
}
getSizes(name) {
if (this.currentComponent>=0) {
return this.components[this.currentComponent].names.getSizes(name);
} else {
return this.names.getSizes(name);
}
}
newTableName() {
return new TableName(this);
}
};

View File

@@ -214,16 +214,38 @@ function getScope(ctx, name, selectors) {
}
function select(v, s) {
s = s || [];
if (s.length == 0) return v;
return select(v[s[0]], s.slice(1));
function select(v, sels) {
if (v.type == "SIGNAL") {
return reduce(v, sels, "sIdx");
} else if (v.type == "COMPONENT") {
return reduce(v, sels, "cIdx");
} else {
const s = sels || [];
if (s.length == 0) return v;
return select(v[s[0]], s.slice(1));
}
}
for (let i=ctx.scopes.length-1; i>=0; i--) {
if (ctx.scopes[i][name]) return select(ctx.scopes[i][name], sels);
}
return null;
function reduce(v, _sels, idxName) {
let sels = _sels || [];
let sizes = v.sizes || [];
let accSizes = [1];
for (let i=sizes.length-1; i>0; i--) {
accSizes = [accSizes[0]*sizes[i], ...accSizes];
}
const res = Object.assign({}, v);
res.sizes = sizes.slice(sels.length);
for (let i=0; i<sels.length; i++) {
res[idxName] += sels[i]*accSizes[i];
}
return res;
}
}
function getScopeLevel(ctx, name) {
@@ -255,6 +277,10 @@ function execTemplateDef(ctx, ast) {
filePath: ctx.filePath,
scopes: copyScope(ctx.scopes)
};
ctx.templates[ast.name] = {
block: ast.block,
params: ast.params
};
}
function execFunctionDef(ctx, ast) {
@@ -272,6 +298,10 @@ function execFunctionDef(ctx, ast) {
filePath: ctx.filePath,
scopes: copyScope(ctx.scopes)
};
ctx.functions[ast.name] = {
block: ast.block,
params: ast.params
};
}
@@ -281,8 +311,6 @@ function execDeclareComponent(ctx, ast) {
if (ast.name.type != "VARIABLE") return error(ctx, ast, "Invalid component name");
if (getScope(ctx, ast.name.name)) return error(ctx, ast, "Name already exists: "+ast.name.name);
const baseName = ctx.currentComponent ? ctx.currentComponent + "." + ast.name.name : ast.name.name;
const sizes=[];
for (let i=0; i< ast.name.selectors.length; i++) {
const size = exec(ctx, ast.name.selectors[i]);
@@ -293,16 +321,13 @@ function execDeclareComponent(ctx, ast) {
sizes.push( size.value.toJSNumber() );
}
const cIdx = ctx.addComponent(ast.name.name, sizes);
scope[ast.name.name] = iterateSelectors(ctx, sizes, baseName, function(fullName) {
ctx.components[fullName] = "UNINSTANTIATED";
return {
type: "COMPONENT",
fullName: fullName
};
});
scope[ast.name.name] = {
type: "COMPONENT",
sizes: sizes,
cIdx: Array.isArray(cIdx) ? cIdx[0] : cIdx
};
return {
type: "VARIABLE",
@@ -333,15 +358,20 @@ function execInstantiateComponet(ctx, vr, fn) {
paramValues.push(v);
}
if (template.params.length != paramValues.length) error(ctx, fn, "Invalid Number of parameters");
if (template.params.length != paramValues.length) return error(ctx, fn, "Invalid Number of parameters");
const vv = getScope(ctx, componentName, vr.selectors);
if (!vv) return error(ctx, vr, "Component not defined"+ componentName);
const vComp = getScope(ctx, componentName, vr.selectors);
if (vComp.type != "COMPONENT") return error(ctx, fn, "Assigning to a non component");
const cIdx = vComp.cIdx;
if (cIdx == -1) return error(ctx, fn, "Component not defined");
let l=1;
for (let i=0; i<vComp.sizes.length; i++) l = l*vComp.sizes[i];
for (let i=0; i<l; i++) {
instantiateComponent(cIdx+i);
}
instantiateComponent(vv);
function instantiateComponent(varVal) {
function instantiateComponent(cIdx) {
function extractValue(v) {
if (Array.isArray(v)) {
@@ -351,23 +381,25 @@ function execInstantiateComponet(ctx, vr, fn) {
}
}
if (Array.isArray(varVal)) {
for (let i =0; i<varVal.length; i++) {
instantiateComponent(varVal[i]);
}
return;
}
if (ctx.components[varVal.fullName] != "UNINSTANTIATED") error(ctx, fn, "Component already instantiated");
if (ctx.components[cIdx]) return error(ctx, fn, "Component already instantiated");
const oldComponent = ctx.currentComponent;
const oldFileName = ctx.fileName;
const oldFilePath = ctx.filePath;
ctx.currentComponent = varVal.fullName;
const oldMain = ctx.main;
ctx.components[ctx.currentComponent] = {
signals: [],
params: {}
if ((componentName == "main")&&(ctx.currentComponent==-1)) {
ctx.main=true;
} else {
ctx.main=false;
}
ctx.currentComponent = cIdx;
ctx.components[cIdx] = {
params: {},
names: ctx.newTableName(),
nInSignals: 0
};
const oldScopes = ctx.scopes;
@@ -379,20 +411,22 @@ function execInstantiateComponet(ctx, vr, fn) {
const scope = {};
for (let i=0; i< template.params.length; i++) {
scope[template.params[i]] = paramValues[i];
ctx.components[ctx.currentComponent].params[template.params[i]] = extractValue(paramValues[i]);
ctx.components[cIdx].params[template.params[i]] = extractValue(paramValues[i]);
}
ctx.components[ctx.currentComponent].template = templateName;
ctx.components[cIdx].template = templateName;
ctx.fileName = template.fileName;
ctx.filePath = template.filePath;
ctx.scopes = copyScope( template.scopes );
ctx.scopes.push(scope);
execBlock(ctx, template.block);
ctx.fileName = oldFileName;
ctx.filePath = oldFilePath;
ctx.currentComponent = oldComponent;
ctx.main = oldMain;
ctx.scopes = oldScopes;
}
}
@@ -461,32 +495,47 @@ function execDeclareSignal(ctx, ast) {
if (ast.name.type != "VARIABLE") return error(ctx, ast, "Invalid component name");
if (getScope(ctx, ast.name.name)) return error(ctx, ast, "Name already exists: "+ast.name.name);
const baseName = ctx.currentComponent ? ctx.currentComponent + "." + ast.name.name : ast.name.name;
const sizes=[];
let totalSize=1;
for (let i=0; i< ast.name.selectors.length; i++) {
const size = exec(ctx, ast.name.selectors[i]);
if (ctx.error) return;
if (size.type != "NUMBER") return error(ctx, ast.name.selectors[i], "expected a number");
sizes.push( size.value.toJSNumber() );
const s = size.value.toJSNumber();
totalSize *= s;
sizes.push( s );
}
scope[ast.name.name] = iterateSelectors(ctx, sizes, baseName, function(fullName) {
ctx.signals[fullName] = {
fullName: fullName,
direction: ast.declareType == "SIGNALIN" ? "IN" : (ast.declareType == "SIGNALOUT" ? "OUT" : ""),
private: ast.private,
component: ctx.currentComponent,
equivalence: "",
alias: [fullName]
let sIdx = ctx.addSignal(ast.name.name, sizes);
if (!Array.isArray(sIdx)) sIdx = [sIdx, sIdx+1];
for (let i=sIdx[0]; i<sIdx[1]; i++) {
ctx.signals[i] = {
o: 0,
e: -1
};
ctx.components[ctx.currentComponent].signals.push(fullName);
return {
type: "SIGNAL",
fullName: fullName,
};
});
if (ast.declareType == "SIGNALIN") {
ctx.signals[i].o |= ctx.IN;
ctx.components[ctx.currentComponent].nInSignals+=1;
}
if (ast.declareType == "SIGNALOUT") {
ctx.signals[i].o |= ctx.OUT;
}
if (ast.private ) {
ctx.signals[i].o |= ctx.PRV;
}
if (ctx.main) {
ctx.signals[i].o |= ctx.MAIN;
}
// ctx.components[ctx.currentComponent].signals.push(i);
}
scope[ast.name.name] = {
type: "SIGNAL",
sizes: sizes,
sIdx: sIdx[0]
};
return {
type: "VARIABLE",
name: ast.name.name,
@@ -535,10 +584,10 @@ function execVariable(ctx, ast) {
if (!v) return error(ctx, ast, "Variable not defined");
// If the signal has an assigned value (constant) just return the constant
if ((v.type == "SIGNAL") && (ctx.signals[v.fullName].value)) {
if ((v.type == "SIGNAL") && (ctx.signals[v.sIdx].value)) {
return {
type: "NUMBER",
value: ctx.signals[v.fullName].value
value: ctx.signals[v.sIdx].value
};
}
let res;
@@ -547,23 +596,43 @@ function execVariable(ctx, ast) {
}
function execPin(ctx, ast) {
const component = getScope(ctx, ast.component.name, ast.component.selectors);
if (!component) return error(ctx, ast.component, "Component does not exists: "+ast.component.name);
if (ctx.error) return;
let signalFullName = component.fullName + "." + ast.pin.name;
const selsC = [];
for (let i=0; i< ast.component.selectors.length; i++) {
const sel = exec(ctx, ast.component.selectors[i]);
if (sel.type != "NUMBER") return error(ctx, ast.pin.selectors[i], "expected a number");
selsC.push(sel.value.toJSNumber());
}
const cIdx = ctx.getComponentIdx(ast.component.name, selsC);
if (cIdx<0) return error(ctx, ast.component, "Component does not exists: "+ast.component.name);
const selsP = [];
for (let i=0; i< ast.pin.selectors.length; i++) {
const sel = exec(ctx, ast.pin.selectors[i]);
if (ctx.error) return;
if (sel.type != "NUMBER") return error(ctx, ast.pin.selectors[i], "expected a number");
signalFullName += "[" + sel.value.toJSNumber() + "]";
selsP.push(sel.value.toJSNumber());
}
if (!ctx.signals[signalFullName]) error(ctx, ast, "Signal not defined:" + signalFullName);
const sIdx = ctx.components[cIdx].names.getSignalIdx(ast.pin.name, selsP);
if (sIdx<0) error(ctx, ast, "Signal not defined:" + buildFullName() );
return {
type: "SIGNAL",
fullName: signalFullName
sIdx: sIdx
};
function buildFullName() {
return ast.component.name + sels2str(selsC) + "." + ast.pin.name + sels2str(selsP);
}
function sels2str(sels) {
let S = "";
for (let i=0; i< sels.length; i++) {
const sel = exec(ctx, ast.pin.selectors[i]);
if (sel.type != "NUMBER") return error(ctx, ast.pin.selectors[i], "expected a number");
S += "[" + sel.value.toString() + "]";
}
return S;
}
}
function execFor(ctx, ast) {
@@ -994,13 +1063,13 @@ function execSignalAssign(ctx, ast) {
if (!dst) return error(ctx, ast, "Signal not defined");
if (dst.type != "SIGNAL") return error(ctx, ast, "Signal assigned to a non signal");
let sDest=ctx.signals[dst.fullName];
if (!sDest) return error(ctx, ast, "Invalid signal: "+dst.fullName);
let sDest=ctx.signals[dst.sIdx];
if (!sDest) return error(ctx, ast, "Invalid signal: "+dst.sIdx);
let isOut = (sDest.component == "main")&&(sDest.direction=="OUT");
while (sDest.equivalence) {
sDest=ctx.signals[sDest.equivalence];
isOut = isOut || ((sDest.component == "main")&&(sDest.direction=="OUT"));
let isOut = (sDest.o & ctx.MAIN)&&(sDest.o & ctx.OUT);
while (sDest.e>=0) {
sDest=ctx.signals[sDest.e];
isOut = isOut || ((sDest.o & ctx.MAIN)&&(sDest.o & ctx.OUT));
}
if (sDest.value) return error(ctx, ast, "Signals cannot be assigned twice");
@@ -1029,18 +1098,17 @@ function execSignalAssign(ctx, ast) {
let assignValue = true;
if (src.type == "SIGNAL") {
let sSrc = ctx.signals[src.fullName];
let isIn = (sSrc.component == "main")&&(sSrc.direction == "IN");
while (sSrc.equivalence) {
sSrc=ctx.signals[sSrc.equivalence];
isIn = isIn || ((sSrc.component == "main")&&(sSrc.direction == "IN"));
let sSrc = ctx.signals[src.sIdx];
let isIn = (sSrc.o & ctx.main)&&(sSrc.o & ctx.IN);
while (sSrc.e>=0) {
sSrc=ctx.signals[sSrc.e];
isIn = isIn || ((sSrc.o & ctx.main)&&(sSrc.o & ctx.IN));
}
// Skip if an out is assigned directly to an input.
if ((!isIn)||(!isOut)) {
sDest.equivalence = src.fullName;
sDest.alias = sDest.alias.concat(src.alias);
while (sDest.equivalence) sDest=ctx.signals[sDest.equivalence];
sDest.e = src.sIdx;
while (sDest.e >= 0) sDest=ctx.signals[sDest.e];
assignValue = false;
}
}
@@ -1095,10 +1163,11 @@ function execInclude(ctx, ast) {
ctx.includedFiles = ctx.includedFiles || [];
if (ctx.includedFiles[incFileName]) return;
ctx.includedFiles[incFileName] = true;
const src = fs.readFileSync(incFileName, "utf8");
ctx.includedFiles[incFileName] = src.split("\n");
if (!src) return error(ctx, ast, "Include file not found: "+incFileName);
const incAst = parser.parse(src);

View File

@@ -188,26 +188,28 @@ function genBlock(ctx, ast) {
}
function genTemplateDef(ctx, ast) {
let S = "function(ctx) ";
const newScope = {};
if (ctx.f) return error(ctx, ast, "Already in function");
ctx.f = ctx.module.addFunction(ast.name);
ctx.c = ctx.f.getCodeBuilder();
ctx.scope = {};
for (let i=0; i< ast.params.length; i++) {
newScope[ast.params[i]] = { type: "VARIABLE" };
ctx.f.addParam(ast.params[i].name, "i32");
ctx.scope[ast.params[i].name] = {
type: "PARAM",
sels: ast.params[i].sels,
getter: () => { return ctx.c.getLocal(ast.params[i].name); },
setter: (v) => { return ctx.c.setLocal(ast.params[i].name, v); }
};
}
ctx.scopes.push(newScope);
S += genBlock(ctx, ast.block);
ctx.scopes.pop();
genBlock(ctx, ast.block);
// const scope = ctx.scopes[ctx.scopes.length-1];
const scope = ctx.scopes[0]; // Scope for templates is top
scope[ast.name] = {
type: "TEMPLATE"
};
ctx.templates[ast.name] = S;
return "";
ctx.scope = null;
ctx.c = null;
ctx.f = null;
}
function genFunctionDef(ctx, ast) {

View File

@@ -60,6 +60,7 @@ QEQ QEQ ERR ERR
const bigInt = require("big-integer");
const __P__ = new bigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
const sONE = 0;
exports.add = add;
exports.mul = mul;
@@ -79,7 +80,7 @@ function signal2lc(a) {
type: "LINEARCOMBINATION",
values: {}
};
lc.values[a.fullName] = bigInt(1);
lc.values[a.sIdx] = bigInt(1);
return lc;
} else {
return a;
@@ -163,10 +164,10 @@ function addLCNum(a,b) {
return { type: "ERROR", errStr: "LinearCombination + undefined" };
}
if (b.value.isZero()) return res;
if (!res.values["one"]) {
res.values["one"]=bigInt(b.value);
if (!res.values[sONE]) {
res.values[sONE]=bigInt(b.value);
} else {
res.values["one"]= res.values["one"].add(b.value).mod(__P__);
res.values[sONE]= res.values[sONE].add(b.value).mod(__P__);
}
return res;
}
@@ -278,16 +279,16 @@ function mulQEQNum(a,b) {
return res;
}
function getSignalValue(ctx, signalName) {
const s = ctx.signals[signalName];
if (s.equivalence != "") {
return getSignalValue(ctx, s.equivalence);
function getSignalValue(ctx, sIdx) {
const s = ctx.signals[sIdx];
if (s.e >= 0) {
return getSignalValue(ctx, s.e);
} else {
const res = {
type: "NUMBER"
};
if (s.value) {
res.value = s.value;
if (s.v) {
res.value = s.v;
}
return res;
}
@@ -297,7 +298,7 @@ function evaluate(ctx, n) {
if (n.type == "NUMBER") {
return n;
} else if (n.type == "SIGNAL") {
return getSignalValue(ctx, n.fullName);
return getSignalValue(ctx, n.sIdx);
} else if (n.type == "LINEARCOMBINATION") {
const v= {
type: "NUMBER",
@@ -362,7 +363,7 @@ function toQEQ(a) {
type: "QEQ",
a: {type: "LINEARCOMBINATION", values: {}},
b: {type: "LINEARCOMBINATION", values: {}},
c: {type: "LINEARCOMBINATION", values: {"one": bigInt(a.value)}}
c: {type: "LINEARCOMBINATION", values: {sONE: bigInt(a.value)}}
};
} else if (a.type == "LINEARCOMBINATION") {
return {
@@ -415,11 +416,11 @@ function toString(a, ctx) {
if (!c.equals(1)) {
S = S + c.toString() + "*";
}
let sigName = k;
let sIdx = k;
if (ctx) {
while (ctx.signals[sigName].equivalence) sigName = ctx.signals[sigName].equivalence;
while (ctx.signals[sIdx].e>=0) sIdx = ctx.signals[sIdx].e;
}
S = S + sigName;
S = S + "[" + sIdx + "]";
}
}
if (S=="") return "0"; else return S;
@@ -437,13 +438,13 @@ function canonize(ctx, a) {
const res = clone(a);
for (let k in a.values) {
let s = k;
while (ctx.signals[s].equivalence) s= ctx.signals[s].equivalence;
if ((typeof(ctx.signals[s].value) != "undefined")&&(k != "one")) {
while (ctx.signals[s].e>=0) s= ctx.signals[s].e;
if ((typeof(ctx.signals[s].value) != "undefined")&&(k != sONE)) {
const v = res.values[k].times(ctx.signals[s].value).mod(__P__);
if (!res.values["one"]) {
res.values["one"]=v;
if (!res.values[sONE]) {
res.values[sONE]=v;
} else {
res.values["one"]= res.values["one"].add(v).mod(__P__);
res.values[sONE]= res.values[sONE].add(v).mod(__P__);
}
delete res.values[k];
} else if (s != k) {

55
src/utils.js Normal file
View File

@@ -0,0 +1,55 @@
const fnv = require("fnv-plus");
module.exports.ident =ident;
module.exports.extractSizes =extractSizes;
module.exports.csArr = csArr;
module.exports.subArray = subArray;
module.exports.accSizes = accSizes;
module.exports.fnvHash = fnvHash;
function ident(text) {
let lines = text.split("\n");
for (let i=0; i<lines.length; i++) {
if (lines[i]) lines[i] = " "+lines[i];
}
return lines.join("\n");
}
function extractSizes (o) {
if (! Array.isArray(o)) return [1, 0];
return [o.length, ...extractSizes(o[0])];
}
// Input [1,2,3]
// Returns " ,1 ,2, 3"
function csArr(_arr) {
let S = "";
const arr = _arr || [];
for (let i=0; i<arr.length; i++) {
S = " ,"+arr[i];
}
return S;
}
function subArray(value, sels) {
if ((!sels) || (sels.length == 0)) return value;
return subArray(value[sels[0]], sels.slice(1));
}
function accSizes(_sizes) {
const sizes = _sizes || [];
const accSizes = [1, 0];
for (let i=sizes.length-1; i>=0; i--) {
accSizes.unshift(accSizes[0]*sizes[i]);
}
return accSizes;
}
function fnvHash(str) {
return fnv.hash(str, 64).hex();
}