diff --git a/package.json b/package.json index ca5f2564..4f7207a0 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "test-web": "nightwatch -e chrome test/web", "start": "electron .", "fuzzbasic": "jsfuzz gen/common/basic/fuzz.js ~/basic/corpus/ --versifier false", + "fuzzhdl": "jsfuzz -r binaryen gen/common/hdl/fuzz.js ~/verilator/corpus/ --versifier false", "machine": "node gen/tools/runmachine.js", "mkdoc": "typedoc --out web/jsdoc src/common/" }, diff --git a/src/common/hdl/fuzz.ts b/src/common/hdl/fuzz.ts new file mode 100644 index 00000000..4687f3b9 --- /dev/null +++ b/src/common/hdl/fuzz.ts @@ -0,0 +1,35 @@ + +//import binaryen = require('binaryen'); +import { HDLModuleJS } from "./hdlruntime"; +import { HDLModuleWASM } from "./hdlwasm"; +import { CompileError, VerilogXMLParser } from "./vxmlparser"; + +export function fuzz(buf) { + var parser = new VerilogXMLParser(); + var str = buf.toString(); + try { + parser.parse(str); + } catch (e) { + if (e instanceof CompileError) return; + throw e; + } + /* + if (0) { + var wmod = new HDLModuleWASM(parser.modules['TOP'], parser.modules['@CONST-POOL@']); + wmod.traceBufferSize = 0x8000; + wmod.maxMemoryMB = 0.25; + wmod.init().then(() => { + wmod.powercycle(); + wmod.tick2(10000); + wmod.dispose(); + }) + } + */ + if (1) { + var jmod = new HDLModuleJS(parser.modules['TOP'], parser.modules['@CONST-POOL@']); + jmod.init(); + jmod.powercycle(); + jmod.tick2(10000); + jmod.dispose(); + } +} diff --git a/src/common/hdl/hdlruntime.ts b/src/common/hdl/hdlruntime.ts index 4d448b39..257dfec0 100644 --- a/src/common/hdl/hdlruntime.ts +++ b/src/common/hdl/hdlruntime.ts @@ -1,6 +1,6 @@ import { byteArrayToString, safe_extend } from "../util"; -import { HDLBinop, HDLBlock, HDLConstant, HDLDataType, HDLExpr, HDLExtendop, HDLFuncCall, HDLModuleDef, HDLModuleRunner, HDLTriop, HDLUnop, HDLValue, HDLVariableDef, HDLVarRef, isArrayItem, isArrayType, isBinop, isBlock, isConstExpr, isFuncCall, isLogicType, isTriop, isUnop, isVarDecl, isVarRef, isWhileop } from "./hdltypes"; +import { HDLBinop, HDLBlock, HDLConstant, HDLDataType, HDLExpr, HDLExtendop, HDLFuncCall, HDLModuleDef, HDLModuleRunner, HDLSourceLocation, HDLTriop, HDLUnop, HDLValue, HDLVariableDef, HDLVarRef, isArrayItem, isArrayType, isBigConstExpr, isBinop, isBlock, isConstExpr, isFuncCall, isLogicType, isTriop, isUnop, isVarDecl, isVarRef, isWhileop } from "./hdltypes"; interface VerilatorUnit { _ctor_var_reset(state) : void; @@ -10,6 +10,18 @@ interface VerilatorUnit { _change_request(state) : boolean; } +export class HDLError extends Error { + obj: any; + $loc: HDLSourceLocation; + constructor(obj: any, msg: string) { + super(msg); + Object.setPrototypeOf(this, HDLError.prototype); + this.obj = obj; + if (obj && obj.$loc) this.$loc = obj.$loc; + if (obj) console.log(obj); + } +} + export class HDLModuleJS implements HDLModuleRunner { mod: HDLModuleDef; @@ -114,7 +126,7 @@ export class HDLModuleJS implements HDLModuleRunner { return; } } - throw new Error(`model did not converge on reset()`) + throw new HDLError(null, `model did not converge on reset()`) } eval() { @@ -139,7 +151,7 @@ export class HDLModuleJS implements HDLModuleRunner { return; } } - throw new Error(`model did not converge on eval()`) + throw new HDLError(null, `model did not converge on eval()`) } tick2(iters: number) { @@ -180,20 +192,20 @@ export class HDLModuleJS implements HDLModuleRunner { if (isArrayItem(e) && isConstExpr(e.expr)) { arr[e.index] = e.expr.cvalue; } else { - throw new Error(`non-const expr in initarray`); + throw new HDLError(dt, `non-const expr in initarray`); } } } return arr; } - throw new Error(`no default value for var type: ${vardef.name}`); + throw new HDLError(dt, `no default value for var type: ${vardef.name}`); } constValue(expr: HDLExpr) : number { if (isConstExpr(expr)) { return expr.cvalue; } else { - throw new Error(`no const value for expr`); + throw new HDLError(expr, `no const value for expr`); } } @@ -220,7 +232,7 @@ export class HDLModuleJS implements HDLModuleRunner { s += ` = ${this.constValue(e)}`; // TODO? } else if (e.initValue != null) { // TODO? - throw new Error(`can't init array here`); + throw new HDLError(e, `can't init array here`); } else if (isLogicType(e.dtype) && e.dtype.left > 31) { // TODO: hack for big ints ($readmem) s += ` = []`; @@ -228,6 +240,8 @@ export class HDLModuleJS implements HDLModuleRunner { return s; } else if (isConstExpr(e)) { return `0x${e.cvalue.toString(16)}`; + } else if (isBigConstExpr(e)) { + return e.bigvalue.toString(); // TODO? } else if (isTriop(e)) { switch (e.op) { case 'if': @@ -239,8 +253,7 @@ export class HDLModuleJS implements HDLModuleRunner { case 'condbound': return `(${this.expr2js(e.cond, {cond:true})} ? ${this.expr2js(e.left)} : ${this.expr2js(e.right)})`; default: - console.log(e); - throw Error(`unknown triop ${e.op}`); + throw new HDLError(e, `unknown triop ${e.op}`); } } else if (isBinop(e)) { switch (e.op) { @@ -260,8 +273,7 @@ export class HDLModuleJS implements HDLModuleRunner { default: var jsop = OP2JS[e.op]; if (!jsop) { - console.log(e); - throw Error(`unknown binop ${e.op}`) + throw new HDLError(e, `unknown binop ${e.op}`) } if (jsop.startsWith('?')) { jsop = jsop.substr(1); @@ -287,9 +299,10 @@ export class HDLModuleJS implements HDLModuleRunner { case 'extends': let shift = 32 - (e as HDLExtendop).widthminv; return `((${this.expr2js(e.left)} << ${shift}) >> ${shift})`; + case 'redxor': + return `this.$$${e.op}(${this.expr2js(e.left)})`; default: - console.log(e); - throw Error(`unknown unop ${e.op}`); + throw new HDLError(e, `unknown unop ${e.op}`); } } else if (isBlock(e)) { // TODO: { e } ? @@ -330,11 +343,11 @@ export class HDLModuleJS implements HDLModuleRunner { return `${this.expr2js(e)}.forEach((a) => a.fill(0))` } else { // TODO: 3d arrays? - throw Error(`unsupported data type for reset: ${JSON.stringify(e.dtype)}`); + throw new HDLError(e, `unsupported data type for reset: ${JSON.stringify(e.dtype)}`); } } } else { - throw Error(`can only reset var refs`); + throw new HDLError(e, `can only reset var refs`); } } @@ -379,16 +392,27 @@ export class HDLModuleJS implements HDLModuleRunner { var strfn = byteArrayToString(barr); // convert to string // parse hex/binary file var strdata = this.getFileData(strfn) as string; - if (strdata == null) throw Error("Could not $readmem '" + strfn + "'"); + if (strdata == null) throw new HDLError(null, "Could not $readmem '" + strfn + "'"); var data = strdata.split('\n').filter(s => s !== '').map(s => parseInt(s, ishex ? 16 : 2)); console.log('$readmem', ishex, strfn, data.length); // copy into destination array - if (memp === null) throw Error("No destination array to $readmem " + strfn); - if (memp.length < data.length) throw Error("Destination array too small to $readmem " + strfn); + if (memp === null) throw new HDLError(null, "No destination array to $readmem " + strfn); + if (memp.length < data.length) throw new HDLError(null, "Destination array too small to $readmem " + strfn); for (i=0; i>1)); r=(r^(r>>2)); r=(r^(r>>4)); r=(r^(r>>8)); r=(r^(r>>16)); + return r; + } + + // + isStopped() { return this.stopped; } isFinished() { return this.finished; } diff --git a/src/common/hdl/hdlwasm.ts b/src/common/hdl/hdlwasm.ts index 10fbfb72..ae75df3c 100644 --- a/src/common/hdl/hdlwasm.ts +++ b/src/common/hdl/hdlwasm.ts @@ -1,6 +1,7 @@ -import { hasDataType, HDLBinop, HDLBlock, HDLConstant, HDLDataType, HDLDataTypeObject, HDLExpr, HDLExtendop, HDLFuncCall, HDLModuleDef, HDLModuleRunner, HDLSourceLocation, HDLTriop, HDLUnop, HDLValue, HDLVariableDef, HDLVarRef, HDLWhileOp, isArrayItem, isArrayType, isBigConstExpr, isBinop, isBlock, isConstExpr, isFuncCall, isLogicType, isTriop, isUnop, isVarDecl, isVarRef, isWhileop } from "./hdltypes"; import binaryen = require('binaryen'); +import { hasDataType, HDLBinop, HDLBlock, HDLConstant, HDLDataType, HDLDataTypeObject, HDLExpr, HDLExtendop, HDLFuncCall, HDLModuleDef, HDLModuleRunner, HDLSourceLocation, HDLTriop, HDLUnop, HDLValue, HDLVariableDef, HDLVarRef, HDLWhileOp, isArrayItem, isArrayType, isBigConstExpr, isBinop, isBlock, isConstExpr, isFuncCall, isLogicType, isTriop, isUnop, isVarDecl, isVarRef, isWhileop } from "./hdltypes"; +import { HDLError } from "./hdlruntime"; const VERILATOR_UNIT_FUNCTIONS = [ "_ctor_var_reset", @@ -27,18 +28,6 @@ const TRACEBUF = "$$tbuf"; /// -export class HDLError extends Error { - obj: any; - $loc: HDLSourceLocation; - constructor(obj: any, msg: string) { - super(msg); - Object.setPrototypeOf(this, HDLError.prototype); - this.obj = obj; - if (obj && obj.$loc) this.$loc = obj.$loc; - if (obj) console.log(obj); - } -} - function getDataTypeSize(dt: HDLDataType) : number { if (isLogicType(dt)) { if (dt.left <= 7) @@ -195,15 +184,13 @@ export class HDLModuleWASM implements HDLModuleRunner { traceEndOffset: number; trace: any; getFileData = null; + maxMemoryMB : number; - constructor(moddef: HDLModuleDef, constpool: HDLModuleDef) { + constructor(moddef: HDLModuleDef, constpool: HDLModuleDef, maxMemoryMB?: number) { this.hdlmod = moddef; this.constpool = constpool; - this.bmod = new binaryen.Module(); - this.genTypes(); - var membytes = this.globals.len; - var memblks = Math.ceil(membytes / 65536); - this.bmod.setMemory(memblks, memblks, MEMORY); // memory is in 64k chunks + this.maxMemoryMB = maxMemoryMB || 16; + this.genMemory(); this.genFuncs(); } @@ -300,11 +287,26 @@ export class HDLModuleWASM implements HDLModuleRunner { if (this.bmod) { this.bmod.dispose(); this.bmod = null; + this.instance = null; + this.databuf = null; + this.data8 = null; + this.data16 = null; + this.data32 = null; } } // + private genMemory() { + this.bmod = new binaryen.Module(); + this.genTypes(); + var membytes = this.globals.len; + if (membytes > this.maxMemoryMB*1024*1024) + throw new HDLError(null, `cannot allocate ${membytes} bytes, limit is ${this.maxMemoryMB} MB`); + var memblks = Math.ceil(membytes / 65536); + this.bmod.setMemory(memblks, memblks, MEMORY); // memory is in 64k chunks + } + private genTypes() { // generate global variables var state = new Struct(); diff --git a/src/common/hdl/vxmlparser.ts b/src/common/hdl/vxmlparser.ts index 3eaa1f11..c622636f 100644 --- a/src/common/hdl/vxmlparser.ts +++ b/src/common/hdl/vxmlparser.ts @@ -1,4 +1,5 @@ +import { HDLError } from "./hdlruntime"; import { HDLAlwaysBlock, HDLArrayItem, HDLBinop, HDLBlock, HDLConstant, HDLDataType, HDLDataTypeObject, HDLExpr, HDLExtendop, HDLFile, HDLFuncCall, HDLHierarchyDef, HDLInstanceDef, HDLLogicType, HDLModuleDef, HDLNativeType, HDLPort, HDLSensItem, HDLSourceLocation, HDLTriop, HDLUnit, HDLUnop, HDLUnpackArray, HDLValue, HDLVariableDef, HDLVarRef, HDLWhileOp, isArrayType, isBinop, isBlock, isConstExpr, isFuncCall, isLogicType, isTriop, isUnop, isVarDecl, isVarRef } from "./hdltypes"; /** @@ -16,6 +17,13 @@ import { HDLAlwaysBlock, HDLArrayItem, HDLBinop, HDLBlock, HDLConstant, HDLDataT * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer */ + export class CompileError extends Error { + constructor(obj: HDLSourceLocation|XMLNode, msg: string) { + super(msg); + Object.setPrototypeOf(this, CompileError.prototype); + } +} + interface XMLNode { type: string; text: string | null; @@ -47,10 +55,11 @@ function parseXMLPoorly(s: string, openfn?: XMLVisitFunction, closefn?: XMLVisit function closetop() { top = stack.pop(); - if (top.type != ident) throw Error("mismatch close tag: " + ident); + if (top == null || top.type != ident) throw new CompileError(node, "mismatch close tag: " + ident); if (closefn) { top.obj = closefn(top); } + if (stack.length == 0) throw new CompileError(null, "close tag without open: " + ident); stack[stack.length - 1].children.push(top); } function parseattrs(as: string): { [id: string]: string } { @@ -79,24 +88,16 @@ function parseXMLPoorly(s: string, openfn?: XMLVisitFunction, closefn?: XMLVisit } if (attrs && attrs.endsWith('/')) closetop(); } else if (content != null) { + if (stack.length == 0) throw new CompileError(null, "content without element"); var txt = escapeXML(content as string).trim(); if (txt.length) stack[stack.length - 1].text = txt; } } - if (stack.length != 1) throw Error("tag not closed"); - if (stack[0].type != '?xml') throw Error("?xml needs to be first element"); + if (stack.length != 1) throw new CompileError(null, "tag not closed"); + if (stack[0].type != '?xml') throw new CompileError(null, "?xml needs to be first element"); return top; } -export class CompileError extends Error { - $loc : HDLSourceLocation; - constructor(msg: string, loc: HDLSourceLocation) { - super(msg); - Object.setPrototypeOf(this, CompileError.prototype); - this.$loc = loc; - } -} - export class VerilogXMLParser implements HDLUnit { files: { [id: string]: HDLFile } = {}; @@ -131,12 +132,13 @@ export class VerilogXMLParser implements HDLUnit { } name2js(s: string) { + if (s == null) throw new CompileError(null, `no name`); return s.replace(/[^a-z0-9_]/gi, '$'); } findChildren(node: XMLNode, type: string, required: boolean) : XMLNode[] { var arr = node.children.filter((n) => n.type == type); - if (arr.length == 0 && required) throw Error(`no child of type ${type}`); + if (arr.length == 0 && required) throw new CompileError(node, `no child of type ${type}`); return arr; } @@ -165,7 +167,7 @@ export class VerilogXMLParser implements HDLUnit { instances: [], vardefs: {}, } - if (this.cur_module) throw new Error(`nested modules not supported`); + if (this.cur_module) throw new CompileError(node, `nested modules not supported`); this.cur_module = module; return module; } @@ -176,8 +178,7 @@ export class VerilogXMLParser implements HDLUnit { this.defer(() => { def.dtype = this.dtypes[dtype_id]; if (!def.dtype) { - console.log(node); - throw Error(`Unknown data type ${dtype_id} for ${node.type}`); + throw new CompileError(node, `Unknown data type ${dtype_id} for ${node.type}`); } }) } @@ -193,19 +194,19 @@ export class VerilogXMLParser implements HDLUnit { else return BigInt('0x' + numstr); } else { - throw Error(`could not parse constant "${s}"`); + throw new CompileError(null, `could not parse constant "${s}"`); } } resolveVar(s: string, mod: HDLModuleDef) : HDLVariableDef { var def = mod.vardefs[s]; - if (def == null) throw Error(`could not resolve variable "${s}"`); + if (def == null) throw new CompileError(null, `could not resolve variable "${s}"`); return def; } resolveModule(s: string) : HDLModuleDef { var mod = this.modules[s]; - if (mod == null) throw Error(`could not resolve module "${s}"`); + if (mod == null) throw new CompileError(null, `could not resolve module "${s}"`); return mod; } @@ -325,7 +326,7 @@ export class VerilogXMLParser implements HDLUnit { } visit_inititem(node: XMLNode) : HDLArrayItem { - if (node.children.length != 1) throw Error('expected 1 children'); + this.expectChildren(node, 1, 1); return { index: parseInt(node.attrs['index']), expr: node.children[0].obj @@ -334,7 +335,7 @@ export class VerilogXMLParser implements HDLUnit { visit_cfunc(node: XMLNode) : HDLBlock { if (this.cur_module == null) { // TODO? - console.log('no module open, skipping', node); + //console.log('no module open, skipping', node); return; } var block = this.visit_begin(node); @@ -363,7 +364,7 @@ export class VerilogXMLParser implements HDLUnit { } visit_port(node: XMLNode) : HDLPort { - if (node.children.length != 1) throw Error('expected 1 children'); + this.expectChildren(node, 1, 1); var varref: HDLPort = { $loc: this.parseSourceLocation(node), name: node.attrs['name'], @@ -379,7 +380,12 @@ export class VerilogXMLParser implements HDLUnit { } visit_module_files(node: XMLNode) { - node.children.forEach((n) => this.files[(n.obj as HDLFile).id].isModule = true); + node.children.forEach((n) => { + if (n.obj) { + var file = this.files[(n.obj as HDLFile).id]; + if (file) file.isModule = true; + } + }); } visit_file(node: XMLNode) { @@ -404,9 +410,12 @@ export class VerilogXMLParser implements HDLUnit { } visit_cells(node: XMLNode) { + this.expectChildren(node, 1, 9999); var hier = node.children[0].obj as HDLHierarchyDef; - var hiername = hier.name; - this.hierarchies[hiername] = hier; + if (hier != null) { + var hiername = hier.name; + this.hierarchies[hiername] = hier; + } } visit_cell(node: XMLNode) : HDLHierarchyDef { @@ -449,8 +458,7 @@ export class VerilogXMLParser implements HDLUnit { default: dtype = this.dtypes[dtypename]; if (dtype == null) { - console.log(node); - throw Error(`unknown data type ${dtypename}`); + throw new CompileError(node, `unknown data type ${dtypename}`); } } this.dtypes[id] = dtype; @@ -471,18 +479,18 @@ export class VerilogXMLParser implements HDLUnit { this.dtypes[id] = dtype; this.defer(() => { dtype.subtype = this.dtypes[sub_dtype_id]; - if (!dtype.subtype) throw Error(`Unknown data type ${sub_dtype_id} for array`); + if (!dtype.subtype) throw new CompileError(node, `Unknown data type ${sub_dtype_id} for array`); }) return dtype; } else { - throw Error(`could not parse constant exprs in array`) + throw new CompileError(node, `could not parse constant exprs in array`) } } visit_senitem(node: XMLNode) : HDLSensItem { var edgeType = node.attrs['edgeType']; if (edgeType != "POS" && edgeType != "NEG") - throw Error("POS/NEG required") + throw new CompileError(node, "POS/NEG required") return { $loc: this.parseSourceLocation(node), edgeType: edgeType, @@ -505,8 +513,13 @@ export class VerilogXMLParser implements HDLUnit { visit_constpool(node: XMLNode) { } + expectChildren(node: XMLNode, low: number, high: number) { + if (node.children.length < low || node.children.length > high) + throw new CompileError(node, `expected between ${low} and ${high} children`); + } + __visit_unop(node: XMLNode) : HDLUnop { - if (node.children.length != 1) throw Error('expected 1 children'); + this.expectChildren(node, 1, 1); var expr: HDLUnop = { $loc: this.parseSourceLocation(node), op: node.type, @@ -521,12 +534,12 @@ export class VerilogXMLParser implements HDLUnit { var unop = this.__visit_unop(node) as HDLExtendop; unop.width = parseInt(node.attrs['width']); unop.widthminv = parseInt(node.attrs['widthminv']); - if (unop.width != 32) throw Error(`extends width ${unop.width} != 32`) + if (unop.width != 32) throw new CompileError(node, `extends width ${unop.width} != 32`) return unop; } __visit_binop(node: XMLNode) : HDLBinop { - if (node.children.length != 2) throw Error('expected 2 children'); + this.expectChildren(node, 2, 2); var expr: HDLBinop = { $loc: this.parseSourceLocation(node), op: node.type, @@ -539,7 +552,7 @@ export class VerilogXMLParser implements HDLUnit { } visit_if(node: XMLNode) : HDLTriop { - if (node.children.length < 2 || node.children.length > 3) throw Error('expected 2 or 3 children'); + this.expectChildren(node, 2, 3); var expr: HDLTriop = { $loc: this.parseSourceLocation(node), op: 'if', @@ -553,7 +566,7 @@ export class VerilogXMLParser implements HDLUnit { // while and for loops visit_while(node: XMLNode) : HDLWhileOp { - if (node.children.length < 2 || node.children.length > 4) throw Error('expected 2-4 children'); + this.expectChildren(node, 2, 4); var expr: HDLWhileOp = { $loc: this.parseSourceLocation(node), op: 'while', @@ -567,7 +580,7 @@ export class VerilogXMLParser implements HDLUnit { } __visit_triop(node: XMLNode) : HDLBinop { - if (node.children.length != 3) throw Error('expected 2 children'); + this.expectChildren(node, 3, 3); var expr: HDLTriop = { $loc: this.parseSourceLocation(node), op: node.type, @@ -677,8 +690,7 @@ export class VerilogXMLParser implements HDLUnit { if (method) { return method.bind(this)(node); } else { - console.log(node); - throw Error(`no visitor for ${node.type}`) + throw new CompileError(node, `no visitor for ${node.type}`) } } diff --git a/src/common/hdl/vxmltest.ts b/src/common/hdl/vxmltest.ts index 62fb60ad..3f75ef43 100644 --- a/src/common/hdl/vxmltest.ts +++ b/src/common/hdl/vxmltest.ts @@ -107,6 +107,6 @@ async function testJSvsWASM() { } } -testJSvsWASM(); -//testWASM().then(testJS); +//testJSvsWASM(); +testWASM().then(testJS).then(testJSvsWASM);