@specs-feup/kadabra
Version:
A Java source-to-source compiler written in Typescript
243 lines (219 loc) • 6.72 kB
text/typescript
import { Class } from "../../Joinpoints.js";
import Timer from "../monitor/Timer.js";
import { convertPrimitive } from "../Utils.js";
/**
* Provides basic functionality to test versions by: <br>
* * adding a new timer, test time, best time, bestVersion, adapt, numRuns and warmup fields, <br>
* * creating basic code to start the adaptation after "numRuns" executions <br>
* * creating basic code to update after a given execution <br> <br>
* must invoke methods "onNewVersion", "ifBetterVersion" and "beforeUpdate" before injecting the adaptation <br>
* must insert the following variables: adapt(), update(), timerStart, timerStop
*/
export function NewVersionTester(
$class: Class,
targetType: string,
timeUnit = "Millis",
numRuns = 1,
warmup = 0,
jumpIfWorse = false
) {
const convert = convertPrimitive(targetType);
const testerInit = `VersionTester.newInstance(${JSON.stringify(
timeUnit
)}, ${numRuns}, ${warmup}, ${jumpIfWorse})`;
const $tester = $class.newField(
["private", "static"],
`weaver.kadabra.monitor.VersionTester<${convert.wrapper}>`,
"tester",
testerInit
);
function onInitialize(code: string) {
return `${$tester}.setOnInitialize(()->{${code}});`;
}
function onFinalize(code: string) {
return `${$tester}.setOnTestFinish(()->{${code}});`;
}
function setTests(versions: string[]) {
const vers = versions.join(",\n");
return `${$tester}.setTests(${vers});`;
}
const timerStop = $tester + ".stopTimer();";
const update = $tester + ".update();";
return {
$tester,
onInitialize,
onFinalize,
setTests,
isAdapting: undefined,
hasFinished: $tester + ".hasFinished()",
start: $tester + ".start();",
pause: $tester + ".pause();",
stop: $tester + ".stop();",
timerStart: $tester + ".startTimer();",
timerStop,
getTime: $tester + ".getTime()",
update,
timerStopAndUpdate: `${timerStop}
${update}`,
testTime: $tester + ".getTestTime()",
bestTime: $tester + ".getBestTime()",
testPos: $tester + ".getTestPos()",
bestPos: $tester + ".getBestPos()",
bestVersion: $tester + ".getBestVersion()",
numRuns: $tester + ".getNumRuns()",
};
}
const mods = ["private", "static"];
export function VersionTester(
$class: Class,
targetType: string,
targetFieldRef: string,
numRuns = 1,
timeUnit = "Millis",
warmup = 0,
jumpIfWorse = false
) {
let newVersionCode = "";
let betterVersionCode = "";
let beforeUpdateCode = "";
let onInitializeCode = "";
function onNewVersion(code: string) {
newVersionCode = code;
}
function ifBetterVersion(code: string) {
betterVersionCode = code;
}
function beforeUpdate(code: string) {
beforeUpdateCode = code;
}
function onInitialize(code: string) {
onInitializeCode = code;
}
let timer = Timer.millisTimer();
switch (timeUnit.toUpperCase()) {
case "MILLIS":
case "MILLISECONDS":
case "MS":
break;
case "NANO":
case "NANOSECONDS":
case "NS":
timer = Timer.nanoTimer();
break;
default:
throw new Error(
`The given time unit cannot be used: ${timeUnit}. Please use "nanoseconds"/"ns" or "milliseconds"/"ms"`
);
}
const convert = convertPrimitive(targetType);
const $warmup = $class.newField(mods, "int", "warmup", "0");
const $numRuns = $class.newField(mods, "int", "numRuns", "0");
const $bestVersion = $class.newField(
mods,
convert.wrapper,
"bestVersion",
"null"
);
const $bestTime = $class.newField(
mods,
"long",
"bestTime",
"java.lang.Long.MAX_VALUE"
);
const $testTime = $class.newField(mods, "long", "testTime", "0");
const $adapt = $class.newField(mods, "boolean", "adapt", "false");
const $init = $class.newField(mods, "boolean", "initialized", "false");
function adapt(alwaysAdapt: boolean, useNewVersionInInit: boolean) {
let header = "";
let newVersionInInit = "";
if (alwaysAdapt) {
// means that will always execute the adaptation, even if "+$adapt+" is false!
header = $adapt + " = true;";
} else {
header = `if(${$adapt})`;
}
if (useNewVersionInInit) {
newVersionInInit = newVersionCode;
}
const resetVars = `
${$testTime} = 0;
${$numRuns} = ${numRuns};
${$warmup} = ${warmup};
`;
return `
${header} { //adapt begin
if(!${$init}){ //Reset all variables so the test starts at the first version and without best time!
${timerStop}
${$bestTime} = Long.MAX_VALUE;
${$testTime} = 0;
${$bestVersion} = null;
${$init} = true;
${resetVars}
${onInitializeCode}
${newVersionInInit}
}
if(${$numRuns} == 0){
boolean improved = false;
//If test version is better than the current best version
if(${$testTime} < ${$bestTime}){
${$bestVersion} = ${targetFieldRef};
${$bestTime} = ${$testTime};
improved = true;
//execute extra code
${betterVersionCode}
}
//execute code that provides a new version
${newVersionCode}
//Reset counters and timers for the next test;
${resetVars}
}
} //adapt end`;
}
const timerStart = timer.start;
const timerStop = timer.stop;
const getTime = timer.getTime;
const ifWorseCode = !jumpIfWorse
? "" //do nothing
: `if(${$testTime} > ${$bestTime}){ //else, If it is taking more time than the best version, then we discard this version
${$numRuns} = 0;
} else`;
function update() {
return `
${beforeUpdateCode}
if (${$adapt}) {
if (${$warmup} == 0) {
${$testTime} += ${timer.getTime};
${ifWorseCode}
${$numRuns}--;
} else {
${$warmup}--;
}
}`;
}
const isAdapting = $adapt.name;
const start = $adapt + " = true;";
const pause = $adapt + " = false;";
const stop = `
${$init} = false;
${$adapt} = false;`;
return {
$testTime,
$bestTime,
$bestVersion,
$numRuns,
$improved: "improved",
ifBetterVersion,
onNewVersion,
beforeUpdate,
onInitialize,
timerStart,
timerStop,
getTime,
adapt,
update,
start,
stop,
pause,
isAdapting,
};
}