From 336b66f4efcf6ed6c6d24680d4ccf21415988322 Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 18 Jun 2024 13:44:54 +1200 Subject: [PATCH] feature: uica / other objective functions (length / stack usage/ uica tp-prediction) --- INSTALL.md | 28 ++-- completion/_cryptopt | 24 ++++ src/CryptOpt.ts | 106 ++++++++------ src/errors/errors.ts | 10 +- src/helper/analyse.ts | 48 +++++-- src/helper/argParse.ts | 23 ++- src/helper/globals.ts | 1 + src/model/model.class.ts | 4 +- src/optimizer/measure.class.ts | 189 +++++++++++++++++++++++++ src/optimizer/optimizer.class.ts | 54 +++++-- src/optimizer/optimizer.helper.ts | 11 +- src/types/AnalyseResult.type.ts | 4 +- src/types/CryptOpt.namespace.ts | 22 +++ src/types/CryptoptGlobals.interface.ts | 1 + src/types/optimizer.types.ts | 9 +- test/optimiser/measure.class.ts | 184 ++++++++++++++++++++++++ test/test-helpers.ts | 2 + 17 files changed, 626 insertions(+), 94 deletions(-) create mode 100644 src/optimizer/measure.class.ts create mode 100644 test/optimiser/measure.class.ts diff --git a/INSTALL.md b/INSTALL.md index 6d1cc59..cf3262d 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,4 +1,4 @@ -# Overview +# Overview We recommend using the Docker based setup to play around with CryptOpt. If you want to actually use CryptOpt for production, the results can typically be improved by running it bare metal. @@ -11,9 +11,9 @@ You can `sudo make install-zsh`, if you want to get `zsh` completion. ``` curl -L https://raw.githubusercontent.com/0xADE1A1DE/CryptOpt/main/Dockerfile > Dockerfile -docker build . -t cryptopt +docker build . -t cryptopt docker run --name CryptOpt -ti cryptopt zsh -# shell changes to 123456# +# shell changes to 123456# ./CryptOpt --help ``` @@ -22,24 +22,24 @@ docker run --name CryptOpt -ti cryptopt zsh 1. [Install Docker](https://docs.docker.com/get-docker) or in the [Install Docker.md](./INSTALL_docker.md). 1. Clone this repository, then change into the directory containing the `Dockerfile`. 1. Build Container -Build the container with `docker build . -t cryptopt`. (`.` is the *build context*. It's the path containing the `Dockerfile`) -This can take a while. (Maybe around 20 minutes, depending on Internet bandwidth and machine) (Note: Depending on your Docker version, it is expected that the some output is red. This is warnings of the build process piped to stderr). -The build was successful if it ends `naming to docker.io/library/cryptopt` (or `Sucessfully tagged cryptopt:latest`) -The build command will create a container image tagged `cryptopt`, where all the dependencies are installed and the projects are built, ready to go. + Build the container with `docker build . -t cryptopt`. (`.` is the _build context_. It's the path containing the `Dockerfile`) + This can take a while. (Maybe around 20 minutes, depending on Internet bandwidth and machine) (Note: Depending on your Docker version, it is expected that the some output is red. This is warnings of the build process piped to stderr). + The build was successful if it ends `naming to docker.io/library/cryptopt` (or `Sucessfully tagged cryptopt:latest`) + The build command will create a container image tagged `cryptopt`, where all the dependencies are installed and the projects are built, ready to go. 1. Run the container image with `docker run --name CryptOpt -ti cryptopt zsh` -> you are now in the built project, your terminal should change to something like `abcdef1234#` - ## Bare Metal -CryptOpt itself will only write files in the operating systems' temp directory (`/tmp/` on Linux) and in its own subdirectories. -It will require internet access to download the (Node.js) runtime and dependencies + +CryptOpt itself will only write files in the operating system's temp directory (`/tmp/` on Linux) and in its own subdirectories. +It will require Internet access to download the (Node.js) runtime and dependencies 1. Install dependencies (will install globally) (c.f. Dockerfile `apt install` command(s)) 1. Install [AssemblyLine](https://0xADE1A1DE.github.io/Assemblyline) (will install globally) 1. Clone the repo with `--recurse-submodules` to also clone submodules for a bunch of useful scripts. 1. Enable performance counters `echo "1" | sudo tee /proc/sys/kernel/perf_event_paranoid` ([MeasureSuite](https://0xADE1A1DE.github.io/MeasureSuite) will otherwise fall back to use `RDTSCP` to count cycles) 1. Build CryptOpt with `make all`. (or `DEBUG=1 make all` if you want debug info and `--verbose` Will slow down execution by around 50%) -CryptOpt already contains pre-built binaries for fiat-crypto. -If you want to build them fresh, too, follow the build instructions in [the Dockerfile](./Dockerfile) or [on Fiat-Cryptography's GitHub](https://github.com/mit-plv/fiat-crypto). -Then copy the standalone-ocaml binaries from `./src/ExtractionOCaml/{dettman_multiplication,solinas_reduction,unsaturated_solinas,word_by_word_montgomery}` to `./src/bridge/fiat-bridge/data` - + CryptOpt already contains pre-built binaries for fiat-crypto. + If you want to build them fresh, too, follow the build instructions in [the Dockerfile](./Dockerfile) or [on Fiat-Cryptography's GitHub](https://github.com/mit-plv/fiat-crypto). + Then copy the standalone-ocaml binaries from `./src/ExtractionOCaml/{dettman_multiplication,solinas_reduction,unsaturated_solinas,word_by_word_montgomery}` to `./src/bridge/fiat-bridge/data` +1. If you want to use uiCA prediction instead of running the code natively, install [uiCA](https://github.com/andreas-abel/uiCA) and create a link to the `uiCA.py` next to `./CryptOpt`, e.g `ln -s ln -s ~/github/uiCA/uiCA.py ~/github/CryptOpt/uiCA`. Then run CryptOpt with `--objectiveFunction=uiCA --uicaarch=SKL` to use the prediction of `uiCA` with the Skylake architecture. (this is currently not supported in the Docker conainer, because I didn't get around doing it.) This feature also needs `asmline` in `PATH`, which comes with installing AssemblyLine globally. This also disables Monkey-Testing. diff --git a/completion/_cryptopt b/completion/_cryptopt index 7588b23..6ab3a36 100644 --- a/completion/_cryptopt +++ b/completion/_cryptopt @@ -37,6 +37,8 @@ _arguments -S -s \ '(-s --seed)'{-s,--seed=}'[Seed to start the randomness with\[number\]]:seed' \ '--framePointer=[Specify how the register for the frame pointer (rbp) is used]:framepointer:->framepointeroptions' \ '--memoryConstraints=[Specify any memory contraints. No contraints, all, or out1--arg1 may be aliased]:memorycontraints:->memorycontraintoptions' \ + '--uicaarch=[use uiCA estimation instead of running on hardware.]:uiCAConstraints:->uiCAOptions' \ + '--objectiveFunction=[use objective Function instead of running on hardware.]:objectiveFunctionConstraints:->objectiveFunctionOptions' \ '(-h,--help)'{-h,--help}'[Help]:get help prompt' \ '(--version)'{-v,--version}'[Version]:Version information' \ && ret=0 @@ -81,6 +83,28 @@ case "$state" in 'all[will read all memroy from arguments (argN\[n\]) before writing any values to outN\[n\]]' \ 'out1-arg1[will not read arg1\[n\] after out1\[n\] has been written]' \ ;; + uiCAOptions) + _values 'uiCAConstraints' \ + 'SNB[Sandy Bridge]' \ + 'IVB[Ivy Bridge]' \ + 'HSW[Haswell]' \ + 'BDW[Broadwell]' \ + 'SKL[Skylake]' \ + 'SKX[Skylake-X]' \ + 'KBL[Kaby Lake]' \ + 'CFL[Coffee Lake]' \ + 'CLX[Coffee Lake-X]' \ + 'ICL[Ice Lake]' \ + 'TGL[Tiger Lake]' \ + 'RKL[Rocket Lake]' \ + ;; + objectiveFunctionOptions) + _values 'objectiveFunctionConstraints' \ + 'cycles[run on hardware]' \ + 'stack[used stack slots (# `mov \[rsp + off\], riz` instructions)]' \ + 'length[length of the function (# instructions)]' \ + 'uiCA[throughput estimation from uiCA. Speicfy arch with --uicaarch]' \ + ;; esac diff --git a/src/CryptOpt.ts b/src/CryptOpt.ts index 4e04b47..4ac4302 100644 --- a/src/CryptOpt.ts +++ b/src/CryptOpt.ts @@ -76,7 +76,7 @@ else if (parsedArgsFromCli.readState) { } } -const { single, bets, betRatio, curve, method, verbose } = parsedArgs; +const { single, bets, betRatio, curve, method, verbose, objectiveFunction, uicaarch } = parsedArgs; if (parsedArgs.resultDir == "") { parsedArgs.resultDir = resolve(process.cwd(), "results"); } @@ -97,7 +97,7 @@ if (!verbose) { const symbolname = new Optimizer(parsedArgs).getSymbolname(true); registerExitHooks({ ...parsedArgs, symbolname }); -type RunResult = { statefile: string; ratio: number; convergence: string[] }; +type RunResult = { statefile: string; ratio: number; convergenceMetric: string[]; convergence: string[] }; async function allBets(evals: number, bets: number): Promise { const runRes = [] as RunResult[]; @@ -147,8 +147,8 @@ async function run(args: OptimizerArgs): Promise { const [statefile] = generateResultFilename({ ...args, symbolname: optimizer.getSymbolname() }); Model.persist(statefile, parsedArgs); - const { ratio, convergence } = Model.getState(); - return { statefile, ratio, convergence }; + const { ratio, convergence, convergenceMetric } = Model.getState(); + return { statefile, ratio, convergence, convergenceMetric }; } let runResults: RunResult[]; @@ -193,22 +193,28 @@ if ("time" in parsed) { const lastConvergence = runResults[runResults.length - 1].convergence; const longestDataRow = lastConvergence.length; -const spaceSeparated = runResults.reduce((arr, { convergence }) => { - // in order to create a matrix for gnuplot, we need to pad with " ?" - const paddingAmount = longestDataRow - convergence.length; - const paddingArray = new Array(paddingAmount).fill(" ? "); - arr.push(convergence.concat(paddingArray).join(" ")); - return arr; -}, [] as string[]); +const spaceSeparated = runResults.reduce( + (arr, { convergence, convergenceMetric }) => { + // in order to create a matrix for gnuplot, we need to pad with " ?" + const paddingAmount = longestDataRow - convergence.length; + const paddingArray = new Array(paddingAmount).fill(" ? "); + arr.conv.push(convergence.concat(paddingArray).join(" ")); + arr.metric.push(convergenceMetric.concat(paddingArray).join(" ")); + return arr; + }, + { conv: [], metric: [] } as { conv: string[]; metric: string[] }, +); -const [datFileFull, gpFileFull, pdfFileFull] = generateResultFilename({ ...parsedArgs, symbolname }, [ - ".dat", - ".gp", - ".pdf", -]); +const [datFileFull, datMetricFileFull, gpFileFull, pdfFileFull] = generateResultFilename( + { ...parsedArgs, symbolname }, + [".dat", "_metric.dat", ".gp", ".pdf"], +); -writeString(datFileFull, spaceSeparated.join("\n")); -process.stdout.write(`Wrote ${cy}${datFileFull}${re} ${spaceSeparated.length}x${longestDataRow}`); +writeString(datFileFull, spaceSeparated.conv.join("\n")); +writeString(datMetricFileFull, spaceSeparated.metric.join("\n")); +process.stdout.write( + `Wrote ${cy}${datFileFull},${datMetricFileFull}${re} ${spaceSeparated.conv.length}x${longestDataRow}`, +); Logger.log(JSON.stringify(times)); const title = [ @@ -218,30 +224,46 @@ const title = [ new Date().toISOString(), hostname(), Object.entries(times).map((k, v) => `Time for ${k}: ${(v / 60).toFixed(2)}min`), -].join(", "); - -writeString( - gpFileFull, - [ - `#!/usr/bin/env gnuplot\n`, - `set title "${title}"`, - "# missing values are the ones from earlier-finished seed-searching evaluations", - `set datafile missing "?"\n`, - "# setting output sizes and filename", - "set terminal pdf size 80cm,20cm", - `set output '${pdfFileFull}'\n`, - "# set x", - 'set xlabel "Mutation"', - "set logscale x 10\n", - "# set y", - // "set yrange [0:2]\n", - `set ylabel "ratio: '${env.CC}-compiled cycle lib'/'cycle good' "\n`, - "# remove legend", - "unset key\n", - "# and plot the matrix with line colors, and a line at y=1 with color 0 (gre)", - `plot "${datFileFull}" matrix using ($1*${PRINT_EVERY}):3:2 linecolor variable with lines, 1 lc 0`, - ].join("\n"), -); + objectiveFunction, + objectiveFunction === "uiCA" ? uicaarch : null, +] + .filter((f) => f) + .join(", "); + +const common = [ + `#!/usr/bin/env gnuplot\n`, + `set title "${title}"`, + "# missing values are the ones from earlier-finished seed-searching evaluations", + `set datafile missing "?"\n`, + "# setting output sizes and filename", + "set terminal pdf size 80cm,20cm", + `set output '${pdfFileFull}'\n`, + "# set x", + 'set xlabel "Mutation"', + "set logscale x 10\n", + "# remove legend", + "unset key\n", + "# set y", + // "set yrange [0:2]\n", + `set ylabel "ratio: '${env.CC}-compiled cycle lib'/'cycle good' "\n`, +]; + +const plots = [ + `"${datFileFull}" matrix using ($1*${PRINT_EVERY}):3:2 with lines linecolor variable`, + "1 lc 0", +]; +if (objectiveFunction !== "cycles") { + common.push("set y2tics", `set y2label "${objectiveFunction}"`); + plots.push( + `"${datMetricFileFull}" matrix using ($1*${PRINT_EVERY}):3:2 with lines linecolor variable dt 3 axes x1y2`, + ); +} + +const plot = [ + "# and plot the matrix with line colors, and a line at y=1 with color 0 (gre)", + `plot ${plots.join(", ")}`, +]; +writeString(gpFileFull, common.concat(plot).join("\n")); process.stdout.write(" Gen Pdf..."); const d = (chunk: Buffer | string) => { diff --git a/src/errors/errors.ts b/src/errors/errors.ts index 08f1b12..130c601 100644 --- a/src/errors/errors.ts +++ b/src/errors/errors.ts @@ -18,23 +18,23 @@ type Err = { exitCode: number; msg: string }; export const ERRORS: { [k: string]: Err } = { measureGeneric: { exitCode: 20, - msg: "measuresuite.measure should return a result but didn't. RES/generic_error_{A,B}.asm as been written for debug.", + msg: "measureUtil.measure should return a result but didn't. RES/generic_error_{A,B}.asm as been written for debug.", }, measureIncorrect: { exitCode: 21, - msg: `measuresuite.measure should return a result but didn't, because the result is not the same as per measureCheck. RES/tested_incorrect{_A.asm,_B.asm,.json} haven been written for debug.`, + msg: `measureUtil.measure should return a result but didn't, because the result is not the same as per measureCheck. RES/tested_incorrect{_A.asm,_B.asm,.json} haven been written for debug.`, }, measureInvalid: { exitCode: 22, - msg: `measuresuite.measure should return a result but didn't, because the ASM string could not be assembled. RES/tested_incorrect{_A.asm,_B.asm,.json} haven been written for debug.`, + msg: `measureUtil.measure should return a result but didn't, because the ASM string could not be assembled. RES/tested_incorrect{_A.asm,_B.asm,.json} haven been written for debug.`, }, measureInsufficientData: { exitCode: 23, - msg: "measuresuite.measure did not yield even one data point.", + msg: "measureUtil.measure did not yield even one data point.", }, measureCannotAnalyze: { exitCode: 24, - msg: "measuresuite.measure did yield data points, but could not be analyzed.", + msg: "measureUtil.measure did yield data points, but could not be analyzed.", }, bcbMakeFail: { exitCode: 30, diff --git a/src/helper/analyse.ts b/src/helper/analyse.ts index 9829e68..7000208 100644 --- a/src/helper/analyse.ts +++ b/src/helper/analyse.ts @@ -19,13 +19,8 @@ import type { AsmFunctionSummary } from "measuresuite"; import * as Stats from "simple-statistics"; import { errorOut, ERRORS } from "@/errors"; -import type { - AnalyseMeasureResultOptions, - AnalyseResult, - MeasureResult, - numTripel, - QuickStats, -} from "@/types"; +import type { AnalyseMeasureResultOptions, AnalyseResult, numTripel, QuickStats } from "@/types"; +import type { CryptOptMeasureResult } from "@/optimizer/measure.class"; /** * @param result - the result to analyse @@ -34,7 +29,7 @@ import type { * @param options.checkCorrectness // throws if not correct; if this options-switch is correct */ export function analyseMeasureResult( - result: MeasureResult | null, + result: CryptOptMeasureResult | null, options: AnalyseMeasureResultOptions, ): AnalyseResult { // assign default values @@ -59,18 +54,28 @@ export function analyseMeasureResult( errorOut(ERRORS.measureInsufficientData); } - const [cc, ca, cb] = result.cycles.map(analyseRow); - const rawMedian: numTripel = [ca.pre.median, cb.pre.median, cc.pre.median]; + let ca: { pre: QuickStats; post: QuickStats }; + let cb: { pre: QuickStats; post: QuickStats }; + let cc: { pre: QuickStats; post: QuickStats } | null = null; + if (result.cycles.length == 3) { + // e.g. assuming its cc ca cb, the default when running the code on the hardware + [cc, ca, cb] = result.cycles.map(analyseRow); + } else { + // e.g. if using estimates rather than execution data + [ca, cb] = result.cycles.map(analyseRow); + cc = null; + } + const rawMedian: numTripel = [ca.pre.median, cb.pre.median, cc?.pre.median ?? -1]; if (rawMedian.some(isNaN)) { console.error("TSNH. Some mean is NaN." + JSON.stringify(result)); process.exit(4); } - const rawStddev: numTripel = [ca.pre.stddev, cb.pre.stddev, cc.pre.stddev]; - const noOutlierMedian: numTripel = [ca.post.median, cb.post.median, cc.post.median]; + const rawStddev: numTripel = [ca.pre.stddev, cb.pre.stddev, cc?.pre.stddev ?? -1]; + const noOutlierMedian: numTripel = [ca.post.median, cb.post.median, cc?.post.median ?? -1]; - const noOutlierStddev: numTripel = [ca.post.stddev, cb.post.stddev, cc.post.stddev]; + const noOutlierStddev: numTripel = [ca.post.stddev, cb.post.stddev, cc?.post.stddev ?? -1]; const scale = (cyc: number): number => cyc / options.batchSize; @@ -105,9 +110,22 @@ function cleanRow(arr: Array, thres = 0): number[] { //helpers -getmedian export function analyseRow(arr: Array): { pre: QuickStats; post: QuickStats } { const cleaned = cleanRow(arr); - // which data to consider; - const [pre, post] = createStatistics(cleaned); + // if we only have one datapoint, no need for statistics. + if (cleaned.length == 1) { + const p = { + median: cleaned[0], + stddev: -1, + n: 1, + }; + return { + pre: p, + post: p, + }; + } + + // which data to consider; i.e remove outliers + const [pre, post] = createStatistics(cleaned); return { pre: { median: Stats.median(pre), diff --git a/src/helper/argParse.ts b/src/helper/argParse.ts index 0c455bc..676bdf6 100644 --- a/src/helper/argParse.ts +++ b/src/helper/argParse.ts @@ -30,7 +30,13 @@ import { } from "@/bridge/fiat-bridge/constants"; import { errorOut, ERRORS } from "@/errors"; -import { FRAME_POINTER_OPTIONS, MEMORY_CONSTRAINTS_OPTIONS, ParsedArgsT } from "../types"; +import { + FRAME_POINTER_OPTIONS, + UICA_OPTIONS, + MEMORY_CONSTRAINTS_OPTIONS, + ParsedArgsT, + OBJECTIVE_FUNCTION_OPTIONS, +} from "../types"; const y = await yargs(process.argv.slice(2)); @@ -216,6 +222,21 @@ export const parsedArgs = y "Defines if memory reads are contraint. 'none' will not enforce anything. All reads are permitted at any time. 'all' enforces that no read from any `argN[n]` happens after any write to `outN[n]`. 'out1-arg1' enforces that no read from arg1[n] is permitted after `out1[n]` has been written (essentially permits mul(r,r,x) and sq(a,a); but not if elemets overlap but not align. (e.g. mul(r+1,r,x)))", choices: MEMORY_CONSTRAINTS_OPTIONS, }) + .option("uicaarch", { + default: "SKL", + string: true, + describe: + "Requires --objectiveFunction=uiCA. Then, CryptOpt uses estimation of uiCA instead of running the code natively. This option specifies the simulated architecture. Ensure uiCA is available in the CryptOpt directory. See INSTALL.md for details.", + choices: UICA_OPTIONS, + }) + .option("objectiveFunction", { + default: "cycles", + string: true, + describe: + "The opjective function which describes which mutation should be kept or discarded. Options: 'cycles' run on hardware, use cycles; stack: size of the used stack slots; length: purely optimise for fewer instructions; uiCA: use uiCA's estimation. Everything but cycles effectively disables monkey-testing becuase the code is not executed.", + + choices: OBJECTIVE_FUNCTION_OPTIONS, + }) .help("help") .alias("h", "help") .wrap(Math.min(160, y.terminalWidth())) diff --git a/src/helper/globals.ts b/src/helper/globals.ts index 99c22be..8b8f69f 100644 --- a/src/helper/globals.ts +++ b/src/helper/globals.ts @@ -19,6 +19,7 @@ import type { CryptoptGlobals } from "@/types"; const globals: CryptoptGlobals = { currentRatio: Infinity, convergence: [] as string[], // numbers, but .toFixed(4) + convergenceMetric: [] as string[], // see interface time: { // in seconds validate: 0, diff --git a/src/model/model.class.ts b/src/model/model.class.ts index 0755b10..e711e33 100644 --- a/src/model/model.class.ts +++ b/src/model/model.class.ts @@ -76,6 +76,7 @@ export class Model { body: Model._nodes, ratio: globals.currentRatio, convergence: globals.convergence, + convergenceMetric: globals.convergenceMetric, seed: Paul.state, time: { validate: globals.time.validate, @@ -102,12 +103,13 @@ export class Model { public static import(filename: fs.PathLike) { const parsed = JSON.parse(fs.readFileSync(filename).toString()); - const { to, body, seed, convergence } = parsed; + const { to, body, seed, convergence, convergenceMetric } = parsed; const m = Model.getInstance(); Model._order = to; Model._nodes = body; Paul.seed = seed; globals.convergence = convergence; + globals.convergenceMetric = convergenceMetric; if ("time" in parsed) { globals.time = parsed.time; } diff --git a/src/optimizer/measure.class.ts b/src/optimizer/measure.class.ts new file mode 100644 index 0000000..d0a22a0 --- /dev/null +++ b/src/optimizer/measure.class.ts @@ -0,0 +1,189 @@ +/** + * Copyright 2023 University of Adelaide + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { writeString } from "@/helper"; +import { OptimizerArgs, UICA_OPTIONS_T, asm } from "@/types"; +import { MeasureResult, Measuresuite } from "measuresuite"; + +import { sha1Hash } from "@/paul"; +import { join } from "path"; +import { tmpdir } from "os"; +import { spawnSync } from "child_process"; +import Logger from "@/helper/Logger.class"; +import { rmdir, rmdirSync } from "fs"; +import { nothing } from "test/test-helpers"; + +export type CryptOptMeasureResult = Omit & { + stats: Omit & { + timer: MeasureResult["stats"]["timer"] | "STACK" | "LENGTH" | UICA_OPTIONS_T; + }; +}; +export default class MeasureUtil { + public timer: CryptOptMeasureResult["stats"]["timer"]; + constructor( + private measuresuite: Measuresuite, + args: OptimizerArgs, + ) { + switch (args.objectiveFunction) { + case "cycles": + this.timer = measuresuite.timer; + this.measure = measuresuite.measure; + break; + case "stack": + this.timer = "STACK"; + this.measure = this.stackMeasure; + break; + case "length": + this.timer = "LENGTH"; + this.measure = this.lenMeasure; + break; + case "uiCA": + this.timer = args.uicaarch; + this.measure = this.uiCAMeasure; + break; + } + } + public destroy() { + if (this.measuresuite) { + return this.measuresuite.destroy(); + } + return 0; + } + public measure: ( + batchSize: number, + numBatches: number, + functions?: string[], + ) => CryptOptMeasureResult | null; + + public measureMeasuresuite( + batchSize: number, + numBatches: number, + functions?: string[], + ): CryptOptMeasureResult | null { + return this.measuresuite.measure(batchSize, numBatches, functions); + } + + public stackMeasure( + _batchSize: number, + _numBatches: number, + functions?: string[], + ): CryptOptMeasureResult | null { + if (!functions) { + return null; + } + + return { + stats: { + numFunctions: functions.length, + runtime: -1, + incorrect: -1, + timer: this.timer, + }, + functions: functions.map(() => ({ type: "ASM", chunks: -1 })), + cycles: functions.map((program: string) => [ + program.split("\n").filter((instruction: asm) => instruction.match("^mov \\[ *rsp ")).length, + ]), + }; + } + private lenMeasure( + _batchSize: number, + _numBatches: number, + functions?: string[], + ): CryptOptMeasureResult | null { + if (!functions) { + return null; + } + + return { + stats: { + numFunctions: functions.length, + runtime: -1, + incorrect: -1, + timer: this.timer, + }, + functions: functions.map(() => ({ type: "ASM", chunks: -1 })), + cycles: functions.map((program: string) => [ + program + .split("\n") + .map((instruction) => instruction.trim()) + .filter( + (instruction: asm) => + instruction.length > 0 && + !instruction.startsWith("GLOBAL") && + !instruction.startsWith("SECTION") && + !instruction.startsWith(";") && + !instruction.match("^[a-zA-Z0-9_]+:$"), + ).length, + ]), + }; + } + private uiCAMeasure( + _batchSize: number, + _numBatches: number, + functions?: string[], + ): CryptOptMeasureResult | null { + if (!functions) { + return null; + } + if (!this.timer) { + Logger.log("ERROR: no uiCA architecture specified."); + return null; + } + + return { + stats: { + numFunctions: functions.length, + runtime: -1, + incorrect: -1, + timer: this.timer, + }, + functions: functions.map(() => ({ type: "ASM", chunks: -1 })), + cycles: functions.map((program: string) => { + const randomString = sha1Hash(Math.ceil(Date.now() * Math.random())).toString(36); + const tmpDir = join(tmpdir(), "CryptOpt.cache", randomString); + const asmFile = "assembly.asm"; + + //Write asm + writeString(join(tmpDir, asmFile), program); + const asmlineArgs = [asmFile, "-o", "binary"]; + + //Write assemble to bin + spawnSync("asmline", asmlineArgs, { cwd: tmpDir }); + + // call uiCA + const uiCAArgs = ["-TPonly", "-arch", this.timer, "-raw", join(tmpDir, "binary.bin")]; + const r = spawnSync("./uiCA", uiCAArgs); + if (r.status != 0) { + throw new Error( + `uiCA command failed. STDERR:${r.stderr}, STDOUT: ${r.stdout}. May be because ./uiCA is not in the cwd.`, + ); + } + const tpout = spawnSync("./uiCA", uiCAArgs).stdout.toString(); + + const tp = Number(tpout); + if (isNaN(tp)) { + console.error( + `uiCA command's output is not a number. Possibly because the selected architecture does not support mulx, etc. STDERR:${r.stderr}, STDOUT: ${r.stdout}.`, + ); + } + Logger.log(`cleaning up uicaTemp Files in folder ${tmpDir}`); + rmdir(tmpDir, () => {}); + + return [tp]; + }), + }; + } +} diff --git a/src/optimizer/optimizer.class.ts b/src/optimizer/optimizer.class.ts index 857463e..af9a3d0 100644 --- a/src/optimizer/optimizer.class.ts +++ b/src/optimizer/optimizer.class.ts @@ -16,7 +16,6 @@ import { execSync } from "child_process"; import { appendFileSync, existsSync, rmSync } from "fs"; -import { Measuresuite } from "measuresuite"; import { tmpdir } from "os"; import { join, resolve as pathResolve } from "path"; @@ -30,6 +29,7 @@ import { LOG_EVERY, padSeed, PRINT_EVERY, + re, shouldProof, toggleFUNCTIONS, writeString, @@ -43,11 +43,12 @@ import type { AnalyseResult, OptimizerArgs } from "@/types"; import { genStatistics, genStatusLine, logMutation, printStartInfo } from "./optimizer.helper"; import { init } from "./optimizer.helper.class"; +import MeasureUtil from "./measure.class"; let choice: CHOICE; export class Optimizer { - private measuresuite: Measuresuite; + private measureutil: MeasureUtil; private libcheckfunctionDirectory: string; // aka. /tmp/CryptOpt.cache/yolo123 private symbolname: string; public getSymbolname(deleteCache = false): string { @@ -56,6 +57,9 @@ export class Optimizer { } return this.symbolname; } + private get isMeasuringDefaultCycles(): boolean { + return this.args.objectiveFunction == "cycles"; + } public constructor(private args: OptimizerArgs) { Paul.seed = args.seed; @@ -65,10 +69,11 @@ export class Optimizer { const { measuresuite, symbolname } = init(this.libcheckfunctionDirectory, args); - this.measuresuite = measuresuite; + this.measureutil = new MeasureUtil(measuresuite, args); this.symbolname = symbolname; globals.convergence = []; + globals.convergenceMetric = []; globals.mutationLog = [ "evaluation,choice,kept,PdetailsBackForwardChosenstepsWaled,DdetailsKindNumhotNumall", ]; @@ -137,11 +142,12 @@ export class Optimizer { printStartInfo({ ...this.args, symbolname: this.symbolname, - counter: this.measuresuite.timer, + counter: this.measureutil.timer, }); let batchSize = 200; const numBatches = 31; let ratioString = ""; + let metricString = ""; let numEvals = 0; const optimistaionStartDate = Date.now(); @@ -204,8 +210,8 @@ export class Optimizer { this.asmStrings[FUNCTIONS.F_B], ); } - // here we need the barriers - const results = this.measuresuite.measure(batchSize, numBatches, [ + + const results = this.measureutil.measureMeasuresuite(batchSize, numBatches, [ this.asmStrings[FUNCTIONS.F_A], this.asmStrings[FUNCTIONS.F_B], ]); @@ -245,11 +251,29 @@ export class Optimizer { } writeString(join(this.args.resultDir, "generic_error_A.asm"), this.asmStrings[FUNCTIONS.F_A]); writeString(join(this.args.resultDir, "generic_error_B.asm"), this.asmStrings[FUNCTIONS.F_B]); + console.error(e); errorOut(ERRORS.measureGeneric); } const [meanrawA, meanrawB, meanrawCheck] = analyseResult.rawMedian; + let metricA = meanrawA; + let metricB = meanrawB; + + // and additionally, if necessary, measure other metrics to base decision on + if (!this.isMeasuringDefaultCycles) { + const resultsMetric = this.measureutil.measure(1, 1, [ + this.asmStrings[FUNCTIONS.F_A], + this.asmStrings[FUNCTIONS.F_B], + ]); + + if (!resultsMetric) { + throw new Error(`Cannot use metric ${this.measureutil.timer}. Abort. `); + } + metricA = resultsMetric.cycles[0][0]; + metricB = resultsMetric.cycles[1][0]; + } + batchSize = Math.ceil((Number(this.args.cyclegoal) / meanrawCheck) * batchSize); // We want to limit for some corner cases. batchSize = Math.min(batchSize, 10000); @@ -263,9 +287,9 @@ export class Optimizer { if ( // A is not worse and A is new - (meanrawA <= meanrawB && currentFunctionIsA()) || + (metricA <= metricB && currentFunctionIsA()) || // or B is not worse and B is new - (meanrawA >= meanrawB && !currentFunctionIsA()) + (metricA >= metricB && !currentFunctionIsA()) ) { Logger.log("kept mutation"); kept = true; @@ -277,7 +301,7 @@ export class Optimizer { kept = false; this.revertFunction(); } - const indexGood = Number(meanrawA > meanrawB); + const indexGood = Number(metricA > metricB); const indexBad = 1 - indexGood; globals.currentRatio = meanrawCheck / Math.min(meanrawB, meanrawA); @@ -286,6 +310,9 @@ export class Optimizer { ratioString = globals.currentRatio /*aka: new ratio*/ .toFixed(4); + metricString = this.isMeasuringDefaultCycles + ? ratioString + : Math.min(metricA, metricB).toPrecision(4); per_second_counter++; if (Date.now() - time > 1000) { @@ -313,6 +340,7 @@ export class Optimizer { no_of_instructions: this.no_of_instructions, numEvals, ratioString, + metricString, show_per_second, stacklength, symbolname: this.symbolname, @@ -321,6 +349,7 @@ export class Optimizer { process.stdout.write(statusline); globals.convergence.push(ratioString); + globals.convergenceMetric.push(metricString); } // Increase Number of evaluations taken. @@ -339,6 +368,9 @@ export class Optimizer { const statistics = genStatistics({ paddedSeed, ratioString, + metricString, + objectiveFunction: this.args.objectiveFunction, + uicaarch: this.args.uicaarch, evals: this.args.evals, elapsed, batchSize, @@ -346,7 +378,7 @@ export class Optimizer { acc: accumulatedTimeSpentByMeasuring, numRevert: this.numRevert, numMut: this.numMut, - counter: this.measuresuite.timer, + counter: this.measureutil.timer, framePointer: this.args.framePointer, memoryConstraints: this.args.memoryConstraints, cyclegoal: this.args.cyclegoal, @@ -390,7 +422,7 @@ export class Optimizer { } Logger.log("done with that current price of assembly code."); this.cleanLibcheckfunctions(); - const v = this.measuresuite.destroy(); + const v = this.measureutil.destroy(); Logger.log(`Wonderful. Done with my work. Destroyed measuresuite (${v}). Time for lunch.`); resolve(0); diff --git a/src/optimizer/optimizer.helper.ts b/src/optimizer/optimizer.helper.ts index 615465c..be485ed 100644 --- a/src/optimizer/optimizer.helper.ts +++ b/src/optimizer/optimizer.helper.ts @@ -41,10 +41,12 @@ export function genStatusLine(a: { numEvals: number; evals: number; ratioString: string; + metricString: string; show_per_second: string; + objectiveFunction: string; }): string { const cyclDeltaR = Math.abs(a.analyseResult.rawMedian[0] - a.analyseResult.rawMedian[1]) - .toString() + .toPrecision(3) .padStart(6); return [ @@ -54,7 +56,7 @@ export function genStatusLine(a: { `${bl}${a.stacklength.toString().padStart(3)}${re}`, `${cy}bs${a.batchSize.toString().padStart(5)}${re}`, `#inst:${cy}${a.no_of_instructions.toString().padStart(4)}${re}`, - `cyclΔ ${gn}${cyclDeltaR}${re}`, + `${a.objectiveFunction.substring(0, 4)}Δ ${gn}${cyclDeltaR}${re}`, // good `${yl}G ${a.analyseResult.batchSizeScaledrawMedian[a.indexGood].toFixed(0).padStart(3)} cycl` + @@ -73,6 +75,7 @@ export function genStatusLine(a: { // lib `${pu}L ${a.analyseResult.batchSizeScaledrawMedian[2].toFixed(0).padStart(3)}${re}`, `${a.ratioString[0] === "0" ? rd : gn}l/g ${bl}${a.ratioString.padStart(6)}${re}`, + `${pu}met${a.metricString.padStart(6)}${re}`, `${bl}${a.choice}${re}`, `${cy}${Model.permutationStats}${re}`, @@ -85,6 +88,9 @@ export function genStatusLine(a: { export function genStatistics(a: { paddedSeed: string; ratioString: string; + metricString: string; + objectiveFunction: string; + uicaarch: string; evals: number; elapsed: number; batchSize: number; @@ -100,6 +106,7 @@ export function genStatistics(a: { return [ `; cpu ${cpus()[0].model}`, `; ratio ${a.ratioString}`, + `; metric ${a.metricString} (${a.objectiveFunction}, ${a.uicaarch})`, `; seed ${a.paddedSeed} `, `; CC / CFLAGS ${CC} / ${CFLAGS} `, `; cyclegoal; ${a.cyclegoal}`, diff --git a/src/types/AnalyseResult.type.ts b/src/types/AnalyseResult.type.ts index f4649e5..4173c60 100644 --- a/src/types/AnalyseResult.type.ts +++ b/src/types/AnalyseResult.type.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { MeasureResult } from "measuresuite"; +import type { CryptOptMeasureResult } from "@/optimizer/measure.class"; export type numTripel = [cyclesA: number, cyclesB: number, cyclesC: number]; @@ -39,7 +39,7 @@ export interface AnalyseResult { chunks: [chunksA: number, chunksB: number]; correct: boolean; - rawResult: MeasureResult; // hopefully just for debug/dev purposes + rawResult: CryptOptMeasureResult; // hopefully just for debug/dev purposes } export interface AnalyseMeasureResultOptions { diff --git a/src/types/CryptOpt.namespace.ts b/src/types/CryptOpt.namespace.ts index aabb8ae..a35489b 100644 --- a/src/types/CryptOpt.namespace.ts +++ b/src/types/CryptOpt.namespace.ts @@ -30,7 +30,29 @@ export const FRAME_POINTER_OPTIONS = ["omit", "save", "constant"] as const; export type FRAME_POINTER_OPTIONS_T = (typeof FRAME_POINTER_OPTIONS)[number]; export const MEMORY_CONSTRAINTS_OPTIONS = ["none", "all", "out1-arg1"] as const; +export const UICA_OPTIONS = [ + "SNB", + "IVB", + "HSW", + "BDW", + "SKL", + "SKX", + "KBL", + "CFL", + "CLX", + "ICL", + "TGL", + "RKL", +] as const; +export const OBJECTIVE_FUNCTION_OPTIONS = [ + "cycles", // default, run on hardware, use cycles + "stack", // size of the used stack slots + "length", // purely optimise for fewer instructions + "uiCA", // use uiCA's estimation +] as const; export type MEMORY_CONSTRAINTS_OPTIONS_T = (typeof MEMORY_CONSTRAINTS_OPTIONS)[number]; +export type UICA_OPTIONS_T = (typeof UICA_OPTIONS)[number]; +export type OBJECTIVE_FUNCTION_OPTIONS_T = (typeof OBJECTIVE_FUNCTION_OPTIONS)[number]; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace CryptOpt { diff --git a/src/types/CryptoptGlobals.interface.ts b/src/types/CryptoptGlobals.interface.ts index 1cf51a9..e51c8c0 100644 --- a/src/types/CryptoptGlobals.interface.ts +++ b/src/types/CryptoptGlobals.interface.ts @@ -17,6 +17,7 @@ export interface CryptoptGlobals { currentRatio: number; convergence: string[]; // numbers, but .toFixed(4) + convergenceMetric: string[]; // numbers, but .toFixed(4) on which co decides to keep. may be cycles or uica or #instr., ... time: { // in seconds validate: number; diff --git a/src/types/optimizer.types.ts b/src/types/optimizer.types.ts index eebd586..9d32f60 100644 --- a/src/types/optimizer.types.ts +++ b/src/types/optimizer.types.ts @@ -17,7 +17,12 @@ import { BRIDGES_T } from "@/bridge"; import { METHOD_T } from "@/bridge/bitcoin-core-bridge"; import { CURVE_T } from "@/bridge/fiat-bridge"; -import { FRAME_POINTER_OPTIONS_T, MEMORY_CONSTRAINTS_OPTIONS_T } from "@/types"; +import { + FRAME_POINTER_OPTIONS_T, + MEMORY_CONSTRAINTS_OPTIONS_T, + OBJECTIVE_FUNCTION_OPTIONS_T, + UICA_OPTIONS_T, +} from "@/types"; export type OptimizerArgs = { evals: number; @@ -38,6 +43,8 @@ export type OptimizerArgs = { preferXmm?: boolean; framePointer: FRAME_POINTER_OPTIONS_T; memoryConstraints: MEMORY_CONSTRAINTS_OPTIONS_T; + uicaarch: UICA_OPTIONS_T; + objectiveFunction: OBJECTIVE_FUNCTION_OPTIONS_T; }; export type ParsedArgsT = OptimizerArgs & { startFromBestJson: boolean; diff --git a/test/optimiser/measure.class.ts b/test/optimiser/measure.class.ts new file mode 100644 index 0000000..97da932 --- /dev/null +++ b/test/optimiser/measure.class.ts @@ -0,0 +1,184 @@ +/** + * Copyright 2023 University of Adelaide + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { afterAll, describe, expect, it, vi } from "vitest"; + +import { nothing } from "../test-helpers"; +import MeasureUtil from "@/optimizer/measure.class"; +import { Measuresuite } from "measuresuite"; +import { OptimizerArgs } from "@/types"; + +const mockLog = vi.spyOn(console, "log").mockImplementation(nothing); +const mockErr = vi.spyOn(console, "error").mockImplementation(nothing); + +describe("stackMeasure", () => { + it("should use the stack counting function", () => { + const mu = new MeasureUtil( + undefined as unknown as Measuresuite, + { objectiveFunction: "stack" } as OptimizerArgs, + ); + expect(mu.measure(0, 0, [])?.stats.timer).toBe("STACK"); + + expect(mu.measure(0, 0, undefined)).toBeNull(); + + expect(mu.measure(0, 0, [])?.cycles).toStrictEqual([]); + expect(mu.measure(0, 0, ["ret", "ret"])?.cycles).toStrictEqual([[0], [0]]); + expect(mu.measure(0, 0, ["ret", "ret", "ret"])?.cycles).toStrictEqual([[0], [0], [0]]); + }); + it("should count the mov [rsp...], xxx; instructons per function", () => { + const mu = new MeasureUtil( + undefined as unknown as Measuresuite, + { objectiveFunction: "stack" } as OptimizerArgs, + ); + const sampleAsm = [ + "SECTION .text", + " GLOBAL fiat_curve25519_carry_square", + "fiat_curve25519_carry_square:", + "sub rsp, 136", + "shlx r9, [ rsi + 0x20 ], r10; x3 <- arg1[4] * 0x2 (shlx does not change the flags)", + "imul r10, [ rsi + 0x18 ], 0x26; x5 <- arg1[3] * 0x26", + "mov [ rsp - 0x80 ], rbx; spilling calSv-rbx to mem", // single space + "mov [rsp - 0x78 ], rbp; spilling calSv-rbp to mem", // no spaces + "mov [ rsp - 0x70 ], r12; spilling calSv-r12 to mem", // multiple spaces + "adcx rcx, [ rsp - 0x18 ]", + "xor rdx, rdx", + "adox r15, [ rsp - 0x8 ]", + "mulx r12, rbp, rax; x12_1, x12_0<- arg1[2] * x2 (_0*_0)", + "; number reverted permutation / tried permutation: 81 / 97 =83.505%", + "; number reverted decision / tried decision: 80 / 102 =78.431%", + ].join("\n"); + const sampleAsm2 = [ + " GLOBAL fiat_rsp", + "sub rsp, 136", + "mov [ rsp - 0x80 ], rbx; spilling calSv-rbx to mem", // single space + "; number reverted decision / tried decision: 80 / 102 =78.431%", + ].join("\n"); + + expect(mu.measure(0, 0, [sampleAsm, sampleAsm2])?.cycles).toStrictEqual([[3], [1]]); + }); +}); +describe("lengthMeasure", () => { + it("should use the length counting function", () => { + const mu = new MeasureUtil( + undefined as unknown as Measuresuite, + { objectiveFunction: "length" } as OptimizerArgs, + ); + expect(mu.measure(0, 0, [])?.stats.timer).toBe("LENGTH"); + expect(mu.measure(0, 0, undefined)).toBeNull(); + expect(mu.measure(0, 0, [])?.cycles).toStrictEqual([]); + }); + it("should count the length of the function, only instructions", () => { + const mu = new MeasureUtil( + undefined as unknown as Measuresuite, + { objectiveFunction: "length" } as OptimizerArgs, + ); + expect(mu.measure(0, 0, ["ret", "ret"])?.cycles).toStrictEqual([[1], [1]]); + expect(mu.measure(0, 0, ["ret", "ret", "ret\npush rbx"])?.cycles).toStrictEqual([[1], [1], [2]]); + const sampleAsm = [ + "SECTION .text", + " GLOBAL fiat_curve25519_carry_square", + "fiat_curve25519_carry_square:", + "sub rsp, 136", + "shlx r9, [ rsi + 0x20 ], r10; x3 <- arg1[4] * 0x2 (shlx do:s not change the flags)", + "imul r10, [ rsi + 0x18 ], 0x26; x5 <- arg1[3] * 0x2:", + "mov [ rsp - 0x80 ], rbx; spilling calSv-rbx to mem", // single space + "mov [rsp - 0x78 ], rbp; spilling calSv-rbp to mem", // no spaces + "mov [ rsp - 0x70 ], r12; spilling calSv-r12 to mem", // multiple spaces + "adcx rcx, [ rsp - 0x18 ] ; :", + "xor rdx, rdx", + "adox r15, [ rsp - 0x8 ]", + "mulx r12, rbp, rax; x12_1, x12_0<- arg1[2] * x2 (_0*_0)", + "; number reverted permutation / tried permutation: 81 / 97 =83.505%", + "; number reverted decision / tried decision: 80 / 102 =78.431%", + ].join("\n"); + const sampleAsm2 = [ + " GLOBAL fiat_rsp", + "sub rsp, 136", + " ", // tab + " ", // space + "", + "mov [ rsp - 0x80 ], rbx; spilling calSv-rbx to mem", // single space + "ret", + "; number reverted decision / tried decision: 80 / 102 =78.431%", + ].join("\n"); + + expect(mu.measure(0, 0, [sampleAsm, sampleAsm2])?.cycles).toStrictEqual([[10], [3]]); + }); +}); + +describe("uiCAMeasure", () => { + it("should use the uica lib and error out if no arch is specified.", () => { + const mu = new MeasureUtil( + undefined as unknown as Measuresuite, + { objectiveFunction: "uiCA" } as OptimizerArgs, + ); + expect(mu.measure(0, 0, [])).toBeNull(); + expect(mu.measure(0, 0, undefined)).toBeNull(); + + const mu2 = new MeasureUtil( + undefined as unknown as Measuresuite, + { objectiveFunction: "uiCA", uicaarch: "SKL" } as OptimizerArgs, + ); + expect(mu2.measure(0, 0, undefined)).toBeNull(); + expect(mu2.measure(0, 0, [])).not.toBeNull(); + expect(mu2.measure(0, 0, [])?.stats.timer).toBe("SKL"); + expect(mu2.measure(0, 0, [])?.cycles).toStrictEqual([]); + }); + + it("should count the length of the function, only instructions", () => { + const mu = new MeasureUtil( + undefined as unknown as Measuresuite, + { objectiveFunction: "uiCA", uicaarch: "SKL" } as OptimizerArgs, + ); + expect(mu.measure(0, 0, ["ret", "ret"])?.cycles).toStrictEqual([[2], [2]]); + expect(mu.measure(0, 0, ["ret", "ret", "ret\npush rbx\npop rcx"])?.cycles).toStrictEqual([[2], [2], [3]]); + const sampleAsm = [ + "SECTION .text", + " GLOBAL fiat_curve25519_carry_square", + "fiat_curve25519_carry_square:", + "sub rsp, 136", + "shlx r9, [ rsi + 0x20 ], r10; x3 <- arg1[4] * 0x2 (shlx do:s not change the flags)", + "imul r10, [ rsi + 0x18 ], 0x26; x5 <- arg1[3] * 0x2:", + "mov [ rsp - 0x80 ], rbx; spilling calSv-rbx to mem", // single space + "mov [rsp - 0x78 ], rbp; spilling calSv-rbp to mem", // no spaces + "mov [ rsp - 0x70 ], r12; spilling calSv-r12 to mem", // multiple spaces + "adcx rcx, [ rsp - 0x18 ] ; :", + "xor rdx, rdx", + "adox r15, [ rsp - 0x8 ]", + "mulx r12, rbp, rax; x12_1, x12_0<- arg1[2] * x2 (_0*_0)", + "; number reverted permutation / tried permutation: 81 / 97 =83.505%", + "; number reverted decision / tried decision: 80 / 102 =78.431%", + ].join("\n"); + + const sampleAsm2 = [ + " GLOBAL fiat_rsp", + "sub rsp, 136", + " ", // tab + " ", // space + "", + "mov [ rsp - 0x80 ], rbx; spilling calSv-rbx to mem", // single space + "ret", + "; number reverted decision / tried decision: 80 / 102 =78.431%", + ].join("\n"); + + expect(mu.measure(0, 0, [sampleAsm, sampleAsm2])?.cycles).toStrictEqual([[3.56], [2.99]]); + }); +}); + +afterAll(() => { + mockLog.mockRestore(); + mockErr.mockRestore(); +}); diff --git a/test/test-helpers.ts b/test/test-helpers.ts index e711d5d..c949e2d 100644 --- a/test/test-helpers.ts +++ b/test/test-helpers.ts @@ -52,6 +52,8 @@ export function getTestArgs(filename: string): OptimizerArgs { redzone: true, framePointer: "omit", memoryConstraints: "none", + objectiveFunction: "cycles", + uicaarch: "RKL", }; }