UNPKG

@specs-feup/clava

Version:

A C/C++ source-to-source compiler written in Typescript

557 lines (480 loc) 16.1 kB
import Io from "@specs-feup/lara/api/lara/Io.js"; import { arrayFromArgs } from "@specs-feup/lara/api/lara/core/LaraCore.js"; import IdGenerator from "@specs-feup/lara/api/lara/util/IdGenerator.js"; import { TimerUnit } from "@specs-feup/lara/api/lara/util/TimeUnits.js"; import Query from "@specs-feup/lara/api/weaver/Query.js"; import { Call, FileJp, FunctionJp } from "../../Joinpoints.js"; import Timer from "../../lara/code/Timer.js"; import ClavaJavaTypes, { ClavaJavaClasses } from "../ClavaJavaTypes.js"; import ClavaJoinPoints from "../ClavaJoinPoints.js"; import MemoiTarget from "./MemoiTarget.js"; import MemoiUtils from "./MemoiUtils.js"; class MemoiGen { private _target: MemoiTarget; private _isEmpty: boolean = true; private _isOnline: boolean = true; private _isUpdateAlways: boolean = false; private _approxBits: number = 0; private _isProf: boolean = false; private _profReportFiles: string[] = []; private _tableSize: number = 65536; private _isDebug: boolean = false; private _applyPolicy = MemoiGen.ApplyPolicy.NOT_EMPTY; private _isZeroSim: boolean = false; private _isResetFunc: boolean = false; constructor(target: MemoiTarget) { this._target = target; } /** * Sets whether to generate a reset function. * */ setResetFunc(isResetFunc: boolean = true) { this._isResetFunc = isResetFunc; } /** * Sets whether to generate code for a 0% sim. * */ setZeroSim(isZeroSim: boolean = true) { this._isZeroSim = isZeroSim; } /** * Sets whether to generate an empty table in the final application. * */ setEmpty(isEmpty: boolean = true) { this._isEmpty = isEmpty; } /** * Sets whether to generate update code in the final application. * */ setOnline(isOnline: boolean = true) { this._isOnline = isOnline; } /** * Sets whether to always update the table on a miss, even if not vacant. * */ setUpdateAlways(isUpdateAlways: boolean = true) { this._isUpdateAlways = isUpdateAlways; } /** * Sets the approximation bits in the final application. * Defaults to 0. * */ setApproxBits(bits: number = 0) { this._approxBits = bits; } /** * Sets the table size in the final application. * Defaults to 65536. * */ setTableSize(size: number = 65536) { const allowedSizes = [ 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, ]; if (!allowedSizes.includes(size)) { throw new Error( "MemoiGen.setTableSize: The possible table sizes are 2^b, with b in [8, 16]." ); } this._tableSize = size; } /** * Sets whether to generate debug code in the final application. * */ setDebug(isDebug: boolean = true) { this._isDebug = isDebug; } /** * Sets the apply policy. * Defaults to MemoiApplyPolicy.NOT_EMPTY. */ setApplyPolicy( policy: MemoiGen.ApplyPolicy = MemoiGen.ApplyPolicy.NOT_EMPTY ) { this._applyPolicy = policy; } /** * Checks files with the given names for reports matching the current target. * @param names - the paths of the report files */ setProfFromFileNames(...names: string[]) { this._isProf = true; this._isEmpty = false; const namesArray = arrayFromArgs(names) as string[]; for (const name of namesArray) { if (!Io.isFile(name)) { throw new Error(`MemoiGen.setProfFromFileNames: ${name} is not a file`); } this._profReportFiles.push(name); } } /** * Checks dir for reports matching the current target. * @param dir - The path to the directory of the report files */ setProfFromDir(dir: string) { this._isProf = true; this._isEmpty = false; this._profReportFiles = Io.getFiles(dir, "*.json", false).map((f) => f.getAbsolutePath() ); } /** * Generates a table for all calls of the target function. * * If a profiling reports are provided, the reports are used to * determine whether to generate for all target or for each single * target. */ generate() { return this._generateAll(); } /** * Generates a table for each call of the target function. * * If a profiling reports are provided, the reports are used to * determine whether to generate for all target or for each single * target. */ generateEach() { this.checkParams("generateEach"); if (this._isProf) { return this.generateFromReport(); } else { return this._generateEach(undefined); } } /** * Generates a table for all calls of the target function. * * If a profiling reports are provided, the reports are used to * determine whether to generate for all target or for each single * target. */ generateAll() { this.checkParams("generateAll"); if (this._isProf) { return this.generateFromReport(); } else { return this._generateAll(undefined); } } private generateFromReport() { const s = new Set<string>(); const reportsMap = ClavaJavaTypes.MemoiReportsMap.fromNames( this._profReportFiles ); // maybe this needs list const siteMap = reportsMap.get(this._target.sig); if (siteMap === null || siteMap === undefined) { throw ( "Could not find report for target " + this._target.sig + " in files [" + this._profReportFiles.join(", ") + "]" ); } for (const site in siteMap) { // merge all reports for this <target, site> const reportPathList = siteMap.get(site); const report = ClavaJavaTypes.MemoiReport.mergeReportsFromNames( reportPathList ) as ClavaJavaClasses.MemoiReport; if (site === "global") { const sTmp = this._generateAll(report); for (const sT of sTmp.values()) { s.add(sT); } } else { const sTmp = this._generateEach(report); for (const sT of sTmp.values()) { s.add(sT); } } } return s; } private _generateEach(report?: ClavaJavaClasses.MemoiReport) { const s = new Set<string>(); const cSig = MemoiUtils.cSig(this._target.sig); const filter = { signature: (s: Call["signature"]) => this._target.sig === MemoiUtils.normalizeSig(s), location: (l: Call["location"]) => report !== undefined ? report.callSites[0] === l : true, // if there is a report, we also filter by site }; for (const $call of Query.search(Call, filter)) { const wrapperName = IdGenerator.next("mw_" + cSig); s.add(wrapperName); $call.wrap(wrapperName); this.generateGeneric(wrapperName, report); } return s; } private _generateAll(report?: ClavaJavaClasses.MemoiReport) { const s = new Set<string>(); const cSig = MemoiUtils.cSig(this._target.sig); const wrapperName = "mw_" + cSig; s.add(wrapperName); for (const $call of Query.search(Call, { signature: (s: Call["signature"]) => this._target.sig === MemoiUtils.normalizeSig(s), })) { $call.wrap(wrapperName); } this.generateGeneric(wrapperName, report); return s; } generatePerfectInst() { const cSig = MemoiUtils.cSig(this._target.sig); const wrapperName = "mw_" + cSig; const globalName = "memoi_target_timer"; const printName = "print_perfect_inst"; // wrap every call to the target for (const $call of Query.search(Call, { signature: (s: string) => this._target.sig === MemoiUtils.normalizeSig(s), })) { $call.wrap(wrapperName); } // change the wrapper by timing around the original call for (const chain of Query.search(FileJp) .search(FunctionJp, { name: wrapperName }) .search(Call) .chain()) { const $file = chain["file"] as FileJp; const $function = chain["function"] as FunctionJp; const $call = chain["call"] as Call; const t = new Timer(TimerUnit.SECONDS); $file.addGlobal(globalName, ClavaJoinPoints.builtinType("double"), "0.0"); const tVar = t.time($call); $function.insertReturn(`memoi_target_timer += ${tVar};`); } // if print_perfect_inst function is found, some other target has dealt with the main code and we're done for (const chain of Query.search(FileJp, { hasMain: true }) .search(FunctionJp, { name: printName }) .chain()) { return; } // change the main function to print the time to a file for (const chain of Query.search(FileJp) .search(FunctionJp, { name: "main" }) .chain()) { const $file = chain["file"] as FileJp; const $main = chain["function"] as FunctionJp; $file.addGlobal(globalName, ClavaJoinPoints.builtinType("double"), "0.0"); $file.addInclude("stdio.h", true); $file.addInclude("sys/stat.h", true); $file.addInclude("sys/types.h", true); $file.addInclude("errno.h", true); $file.addInclude("string.h", true); $file.addInclude("libgen.h", true); $main.insertReturn(printName + "(argv[0]);"); $main.insertBefore("void " + printName + "(char*);"); const $function = $file.addFunction(printName) as FunctionJp; $function.setParamsFromStrings(["char* name"]); $function.insertReturn(` errno = 0; char* prog_name = basename(name); char* file_name = malloc(strlen(prog_name) + strlen("${globalName}.txt") + 1 + 1); sprintf(file_name, "%s.%s", prog_name, "${globalName}.txt"); FILE * ${globalName}_file = fopen (file_name,"a"); if(${globalName}_file == NULL) { perror("Could not create file for memoi perfect instrumentation"); } else { fprintf(${globalName}_file, "%f\n", ${globalName}); fclose(${globalName}_file); } `); } } private generateGeneric( wrapperName: string, report?: ClavaJavaClasses.MemoiReport ) { for (const chain of Query.search(FileJp) .search(FunctionJp, { name: wrapperName }) .search(Call) .chain()) { const $file = chain["file"] as FileJp; const $function = chain["function"] as FunctionJp; const $call = chain["call"] as Call; let dmt: any | undefined = undefined; if (report !== undefined) { // build the DMT and test the apply policy dmt = ClavaJavaTypes.MemoiCodeGen.generateDmt( this._tableSize, report, this._applyPolicy ); if (!dmt.testPolicy()) { console.log( this._target.sig + "[" + report.callSites[0] + "]" + " fails the apply policy " + this._applyPolicy + ". Not applying memoization." ); return; } } let updatesName: string | undefined = undefined; if (this._isDebug) { const totalName = wrapperName + "_total_calls"; const missesName = wrapperName + "_total_misses"; $file.addGlobal( totalName, ClavaJoinPoints.builtinType("unsigned int"), "0" ); $file.addGlobal( missesName, ClavaJoinPoints.builtinType("unsigned int"), "0" ); $call.insert("before", `${totalName}++;`); $call.insert("after", `${missesName}++;`); if (this._isOnline) { updatesName = wrapperName + "_total_updates"; $file.addGlobal( updatesName, ClavaJoinPoints.builtinType("unsigned int"), "0" ); } this.addMainDebug(totalName, missesName, updatesName, wrapperName); } // generate the table (and reset) code and add it before the wrapper const tableCode = ClavaJavaTypes.MemoiCodeGen.generateTableCode( dmt.getTable(), this._tableSize, this._target.numInputs, this._target.numOutputs, this._isOnline, this._isResetFunc, this._isZeroSim ? true : this._isEmpty, // whether this is an empty table wrapperName ) as string; $function.insert("before", tableCode); // generate the logic code and add it before the original call const paramNames = []; for (const $param of $function.params) { paramNames.push($param.name); } const logicCode = ClavaJavaTypes.MemoiCodeGen.generateLogicCode( this._tableSize, paramNames, this._approxBits, this._target.numInputs, this._target.numOutputs, this._target.inputTypes, this._target.outputTypes, wrapperName ) as string; $call.insert("before", logicCode); if (this._isOnline) { const updateCode = ClavaJavaTypes.MemoiCodeGen.generateUpdateCode( this._tableSize, paramNames, this._isUpdateAlways, this._target.numInputs, this._target.numOutputs, updatesName, this._isZeroSim, wrapperName ) as string; $call.insert("after", updateCode); } $file.addInclude("stdint.h", true); } } private addMainDebug( totalName: string, missesName: string, updatesName: string | undefined, wrapperName: string ) { const chain = Query.search(FileJp) .search(FunctionJp, { name: "main" }) .chain(); const firstAndOnly = chain[0]; if (firstAndOnly === undefined) { console.log( "Could not find a main function. It may be impossible to print debug stats if this is a library code." ); return; } const $file = firstAndOnly["file"] as FileJp; const $function = firstAndOnly["function"] as FunctionJp; $file.addGlobal( totalName, ClavaJoinPoints.builtinType("unsigned int"), "0" ); $file.addGlobal( missesName, ClavaJoinPoints.builtinType("unsigned int"), "0" ); if (this._isOnline) { $file.addGlobal( updatesName!, ClavaJoinPoints.builtinType("unsigned int"), "0" ); } $file.addInclude("stdio.h", true); $file.addInclude("sys/stat.h", true); $file.addInclude("sys/types.h", true); $file.addInclude("errno.h", true); let updatesStringCode = ""; let updatesValuesCode = ""; if (this._isOnline) { updatesStringCode = ', \\"updates\\": %u'; updatesValuesCode = ", " + updatesName!; } const json = ` { errno = 0; int dir_result = mkdir("memoi-exec-report", 0755); if(dir_result != 0 && errno != EEXIST){ perror("Could not create directory for memoi execution reports"); } else{ errno = 0; FILE * _memoi_rep_file = fopen ("memoi-exec-report/${wrapperName}.json","w"); if(_memoi_rep_file == NULL) { perror("Could not create file for memoi execution report"); } else { fprintf(_memoi_rep_file, "{"name": "${wrapperName}", "total": %u, "hits": %u, "misses": %u${updatesStringCode}}\n", ${totalName}, ${totalName} - ${missesName}, ${missesName}${updatesValuesCode}); fclose(_memoi_rep_file); } } } `; $function.insertReturn(json); } private checkParams(source: string) { if (!((this._isEmpty && this._isOnline) || !this._isEmpty)) { throw new Error( `MemoiGen.${source}: Can't have empty and offline table.` ); } if (!(this._isEmpty ? !this._isProf : this._isProf)) { throw new Error( "MemoiGen.${source}: Empty table and profile are mutually exclusive table." ); } } } namespace MemoiGen { export enum ApplyPolicy { ALWAYS = "ALWAYS", NOT_EMPTY = "NOT_EMPTY", OVER_25_PCT = "OVER_25_PCT", OVER_50_PCT = "OVER_50_PCT", OVER_75_PCT = "OVER_75_PCT", OVER_90_PCT = "OVER_90_PCT", } } export default MemoiGen;