Skip to content

Commit 336b66f

Browse files
committed
feature: uica / other objective functions (length / stack usage/ uica tp-prediction)
1 parent 54d0c41 commit 336b66f

17 files changed

+626
-94
lines changed

INSTALL.md

+14-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Overview
1+
# Overview
22

33
We recommend using the Docker based setup to play around with CryptOpt.
44
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.
1111

1212
```
1313
curl -L https://raw.githubusercontent.com/0xADE1A1DE/CryptOpt/main/Dockerfile > Dockerfile
14-
docker build . -t cryptopt
14+
docker build . -t cryptopt
1515
docker run --name CryptOpt -ti cryptopt zsh
16-
# shell changes to 123456#
16+
# shell changes to 123456#
1717
./CryptOpt --help
1818
```
1919

@@ -22,24 +22,24 @@ docker run --name CryptOpt -ti cryptopt zsh
2222
1. [Install Docker](https://docs.docker.com/get-docker) or in the [Install Docker.md](./INSTALL_docker.md).
2323
1. Clone this repository, then change into the directory containing the `Dockerfile`.
2424
1. Build Container
25-
Build the container with `docker build . -t cryptopt`. (`.` is the *build context*. It's the path containing the `Dockerfile`)
26-
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).
27-
The build was successful if it ends `naming to docker.io/library/cryptopt` (or `Sucessfully tagged cryptopt:latest`)
28-
The build command will create a container image tagged `cryptopt`, where all the dependencies are installed and the projects are built, ready to go.
25+
Build the container with `docker build . -t cryptopt`. (`.` is the _build context_. It's the path containing the `Dockerfile`)
26+
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).
27+
The build was successful if it ends `naming to docker.io/library/cryptopt` (or `Sucessfully tagged cryptopt:latest`)
28+
The build command will create a container image tagged `cryptopt`, where all the dependencies are installed and the projects are built, ready to go.
2929

3030
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#`
3131

32-
3332
## Bare Metal
34-
CryptOpt itself will only write files in the operating systems' temp directory (`/tmp/` on Linux) and in its own subdirectories.
35-
It will require internet access to download the (Node.js) runtime and dependencies
33+
34+
CryptOpt itself will only write files in the operating system's temp directory (`/tmp/` on Linux) and in its own subdirectories.
35+
It will require Internet access to download the (Node.js) runtime and dependencies
3636

3737
1. Install dependencies (will install globally) (c.f. Dockerfile `apt install` command(s))
3838
1. Install [AssemblyLine](https://0xADE1A1DE.github.io/Assemblyline) (will install globally)
3939
1. Clone the repo with `--recurse-submodules` to also clone submodules for a bunch of useful scripts.
4040
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)
4141
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%)
42-
CryptOpt already contains pre-built binaries for fiat-crypto.
43-
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).
44-
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`
45-
42+
CryptOpt already contains pre-built binaries for fiat-crypto.
43+
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).
44+
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`
45+
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.

completion/_cryptopt

+24
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ _arguments -S -s \
3737
'(-s --seed)'{-s,--seed=}'[Seed to start the randomness with\[number\]]:seed' \
3838
'--framePointer=[Specify how the register for the frame pointer (rbp) is used]:framepointer:->framepointeroptions' \
3939
'--memoryConstraints=[Specify any memory contraints. No contraints, all, or out1--arg1 may be aliased]:memorycontraints:->memorycontraintoptions' \
40+
'--uicaarch=[use uiCA estimation instead of running on hardware.]:uiCAConstraints:->uiCAOptions' \
41+
'--objectiveFunction=[use objective Function instead of running on hardware.]:objectiveFunctionConstraints:->objectiveFunctionOptions' \
4042
'(-h,--help)'{-h,--help}'[Help]:get help prompt' \
4143
'(--version)'{-v,--version}'[Version]:Version information' \
4244
&& ret=0
@@ -81,6 +83,28 @@ case "$state" in
8183
'all[will read all memroy from arguments (argN\[n\]) before writing any values to outN\[n\]]' \
8284
'out1-arg1[will not read arg1\[n\] after out1\[n\] has been written]' \
8385
;;
86+
uiCAOptions)
87+
_values 'uiCAConstraints' \
88+
'SNB[Sandy Bridge]' \
89+
'IVB[Ivy Bridge]' \
90+
'HSW[Haswell]' \
91+
'BDW[Broadwell]' \
92+
'SKL[Skylake]' \
93+
'SKX[Skylake-X]' \
94+
'KBL[Kaby Lake]' \
95+
'CFL[Coffee Lake]' \
96+
'CLX[Coffee Lake-X]' \
97+
'ICL[Ice Lake]' \
98+
'TGL[Tiger Lake]' \
99+
'RKL[Rocket Lake]' \
100+
;;
101+
objectiveFunctionOptions)
102+
_values 'objectiveFunctionConstraints' \
103+
'cycles[run on hardware]' \
104+
'stack[used stack slots (# `mov \[rsp + off\], riz` instructions)]' \
105+
'length[length of the function (# instructions)]' \
106+
'uiCA[throughput estimation from uiCA. Speicfy arch with --uicaarch]' \
107+
;;
84108
esac
85109

86110

src/CryptOpt.ts

+64-42
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ else if (parsedArgsFromCli.readState) {
7676
}
7777
}
7878

79-
const { single, bets, betRatio, curve, method, verbose } = parsedArgs;
79+
const { single, bets, betRatio, curve, method, verbose, objectiveFunction, uicaarch } = parsedArgs;
8080
if (parsedArgs.resultDir == "") {
8181
parsedArgs.resultDir = resolve(process.cwd(), "results");
8282
}
@@ -97,7 +97,7 @@ if (!verbose) {
9797
const symbolname = new Optimizer(parsedArgs).getSymbolname(true);
9898
registerExitHooks({ ...parsedArgs, symbolname });
9999

100-
type RunResult = { statefile: string; ratio: number; convergence: string[] };
100+
type RunResult = { statefile: string; ratio: number; convergenceMetric: string[]; convergence: string[] };
101101

102102
async function allBets(evals: number, bets: number): Promise<RunResult[]> {
103103
const runRes = [] as RunResult[];
@@ -147,8 +147,8 @@ async function run(args: OptimizerArgs): Promise<RunResult> {
147147

148148
const [statefile] = generateResultFilename({ ...args, symbolname: optimizer.getSymbolname() });
149149
Model.persist(statefile, parsedArgs);
150-
const { ratio, convergence } = Model.getState();
151-
return { statefile, ratio, convergence };
150+
const { ratio, convergence, convergenceMetric } = Model.getState();
151+
return { statefile, ratio, convergence, convergenceMetric };
152152
}
153153

154154
let runResults: RunResult[];
@@ -193,22 +193,28 @@ if ("time" in parsed) {
193193
const lastConvergence = runResults[runResults.length - 1].convergence;
194194
const longestDataRow = lastConvergence.length;
195195

196-
const spaceSeparated = runResults.reduce((arr, { convergence }) => {
197-
// in order to create a matrix for gnuplot, we need to pad with " ?"
198-
const paddingAmount = longestDataRow - convergence.length;
199-
const paddingArray = new Array(paddingAmount).fill(" ? ");
200-
arr.push(convergence.concat(paddingArray).join(" "));
201-
return arr;
202-
}, [] as string[]);
196+
const spaceSeparated = runResults.reduce(
197+
(arr, { convergence, convergenceMetric }) => {
198+
// in order to create a matrix for gnuplot, we need to pad with " ?"
199+
const paddingAmount = longestDataRow - convergence.length;
200+
const paddingArray = new Array(paddingAmount).fill(" ? ");
201+
arr.conv.push(convergence.concat(paddingArray).join(" "));
202+
arr.metric.push(convergenceMetric.concat(paddingArray).join(" "));
203+
return arr;
204+
},
205+
{ conv: [], metric: [] } as { conv: string[]; metric: string[] },
206+
);
203207

204-
const [datFileFull, gpFileFull, pdfFileFull] = generateResultFilename({ ...parsedArgs, symbolname }, [
205-
".dat",
206-
".gp",
207-
".pdf",
208-
]);
208+
const [datFileFull, datMetricFileFull, gpFileFull, pdfFileFull] = generateResultFilename(
209+
{ ...parsedArgs, symbolname },
210+
[".dat", "_metric.dat", ".gp", ".pdf"],
211+
);
209212

210-
writeString(datFileFull, spaceSeparated.join("\n"));
211-
process.stdout.write(`Wrote ${cy}${datFileFull}${re} ${spaceSeparated.length}x${longestDataRow}`);
213+
writeString(datFileFull, spaceSeparated.conv.join("\n"));
214+
writeString(datMetricFileFull, spaceSeparated.metric.join("\n"));
215+
process.stdout.write(
216+
`Wrote ${cy}${datFileFull},${datMetricFileFull}${re} ${spaceSeparated.conv.length}x${longestDataRow}`,
217+
);
212218

213219
Logger.log(JSON.stringify(times));
214220
const title = [
@@ -218,30 +224,46 @@ const title = [
218224
new Date().toISOString(),
219225
hostname(),
220226
Object.entries(times).map((k, v) => `Time for ${k}: ${(v / 60).toFixed(2)}min`),
221-
].join(", ");
222-
223-
writeString(
224-
gpFileFull,
225-
[
226-
`#!/usr/bin/env gnuplot\n`,
227-
`set title "${title}"`,
228-
"# missing values are the ones from earlier-finished seed-searching evaluations",
229-
`set datafile missing "?"\n`,
230-
"# setting output sizes and filename",
231-
"set terminal pdf size 80cm,20cm",
232-
`set output '${pdfFileFull}'\n`,
233-
"# set x",
234-
'set xlabel "Mutation"',
235-
"set logscale x 10\n",
236-
"# set y",
237-
// "set yrange [0:2]\n",
238-
`set ylabel "ratio: '${env.CC}-compiled cycle lib'/'cycle good' "\n`,
239-
"# remove legend",
240-
"unset key\n",
241-
"# and plot the matrix with line colors, and a line at y=1 with color 0 (gre)",
242-
`plot "${datFileFull}" matrix using ($1*${PRINT_EVERY}):3:2 linecolor variable with lines, 1 lc 0`,
243-
].join("\n"),
244-
);
227+
objectiveFunction,
228+
objectiveFunction === "uiCA" ? uicaarch : null,
229+
]
230+
.filter((f) => f)
231+
.join(", ");
232+
233+
const common = [
234+
`#!/usr/bin/env gnuplot\n`,
235+
`set title "${title}"`,
236+
"# missing values are the ones from earlier-finished seed-searching evaluations",
237+
`set datafile missing "?"\n`,
238+
"# setting output sizes and filename",
239+
"set terminal pdf size 80cm,20cm",
240+
`set output '${pdfFileFull}'\n`,
241+
"# set x",
242+
'set xlabel "Mutation"',
243+
"set logscale x 10\n",
244+
"# remove legend",
245+
"unset key\n",
246+
"# set y",
247+
// "set yrange [0:2]\n",
248+
`set ylabel "ratio: '${env.CC}-compiled cycle lib'/'cycle good' "\n`,
249+
];
250+
251+
const plots = [
252+
`"${datFileFull}" matrix using ($1*${PRINT_EVERY}):3:2 with lines linecolor variable`,
253+
"1 lc 0",
254+
];
255+
if (objectiveFunction !== "cycles") {
256+
common.push("set y2tics", `set y2label "${objectiveFunction}"`);
257+
plots.push(
258+
`"${datMetricFileFull}" matrix using ($1*${PRINT_EVERY}):3:2 with lines linecolor variable dt 3 axes x1y2`,
259+
);
260+
}
261+
262+
const plot = [
263+
"# and plot the matrix with line colors, and a line at y=1 with color 0 (gre)",
264+
`plot ${plots.join(", ")}`,
265+
];
266+
writeString(gpFileFull, common.concat(plot).join("\n"));
245267

246268
process.stdout.write(" Gen Pdf...");
247269
const d = (chunk: Buffer | string) => {

src/errors/errors.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,23 @@ type Err = { exitCode: number; msg: string };
1818
export const ERRORS: { [k: string]: Err } = {
1919
measureGeneric: {
2020
exitCode: 20,
21-
msg: "measuresuite.measure should return a result but didn't. RES/generic_error_{A,B}.asm as been written for debug.",
21+
msg: "measureUtil.measure should return a result but didn't. RES/generic_error_{A,B}.asm as been written for debug.",
2222
},
2323
measureIncorrect: {
2424
exitCode: 21,
25-
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.`,
25+
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.`,
2626
},
2727
measureInvalid: {
2828
exitCode: 22,
29-
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.`,
29+
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.`,
3030
},
3131
measureInsufficientData: {
3232
exitCode: 23,
33-
msg: "measuresuite.measure did not yield even one data point.",
33+
msg: "measureUtil.measure did not yield even one data point.",
3434
},
3535
measureCannotAnalyze: {
3636
exitCode: 24,
37-
msg: "measuresuite.measure did yield data points, but could not be analyzed.",
37+
msg: "measureUtil.measure did yield data points, but could not be analyzed.",
3838
},
3939
bcbMakeFail: {
4040
exitCode: 30,

src/helper/analyse.ts

+33-15
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,8 @@ import type { AsmFunctionSummary } from "measuresuite";
1919
import * as Stats from "simple-statistics";
2020

2121
import { errorOut, ERRORS } from "@/errors";
22-
import type {
23-
AnalyseMeasureResultOptions,
24-
AnalyseResult,
25-
MeasureResult,
26-
numTripel,
27-
QuickStats,
28-
} from "@/types";
22+
import type { AnalyseMeasureResultOptions, AnalyseResult, numTripel, QuickStats } from "@/types";
23+
import type { CryptOptMeasureResult } from "@/optimizer/measure.class";
2924

3025
/**
3126
* @param result - the result to analyse
@@ -34,7 +29,7 @@ import type {
3429
* @param options.checkCorrectness // throws if not correct; if this options-switch is correct
3530
*/
3631
export function analyseMeasureResult(
37-
result: MeasureResult | null,
32+
result: CryptOptMeasureResult | null,
3833
options: AnalyseMeasureResultOptions,
3934
): AnalyseResult {
4035
// assign default values
@@ -59,18 +54,28 @@ export function analyseMeasureResult(
5954
errorOut(ERRORS.measureInsufficientData);
6055
}
6156

62-
const [cc, ca, cb] = result.cycles.map(analyseRow);
63-
const rawMedian: numTripel = [ca.pre.median, cb.pre.median, cc.pre.median];
57+
let ca: { pre: QuickStats; post: QuickStats };
58+
let cb: { pre: QuickStats; post: QuickStats };
59+
let cc: { pre: QuickStats; post: QuickStats } | null = null;
60+
if (result.cycles.length == 3) {
61+
// e.g. assuming its cc ca cb, the default when running the code on the hardware
62+
[cc, ca, cb] = result.cycles.map(analyseRow);
63+
} else {
64+
// e.g. if using estimates rather than execution data
65+
[ca, cb] = result.cycles.map(analyseRow);
66+
cc = null;
67+
}
68+
const rawMedian: numTripel = [ca.pre.median, cb.pre.median, cc?.pre.median ?? -1];
6469

6570
if (rawMedian.some(isNaN)) {
6671
console.error("TSNH. Some mean is NaN." + JSON.stringify(result));
6772
process.exit(4);
6873
}
6974

70-
const rawStddev: numTripel = [ca.pre.stddev, cb.pre.stddev, cc.pre.stddev];
71-
const noOutlierMedian: numTripel = [ca.post.median, cb.post.median, cc.post.median];
75+
const rawStddev: numTripel = [ca.pre.stddev, cb.pre.stddev, cc?.pre.stddev ?? -1];
76+
const noOutlierMedian: numTripel = [ca.post.median, cb.post.median, cc?.post.median ?? -1];
7277

73-
const noOutlierStddev: numTripel = [ca.post.stddev, cb.post.stddev, cc.post.stddev];
78+
const noOutlierStddev: numTripel = [ca.post.stddev, cb.post.stddev, cc?.post.stddev ?? -1];
7479

7580
const scale = (cyc: number): number => cyc / options.batchSize;
7681

@@ -105,9 +110,22 @@ function cleanRow(arr: Array<number | undefined>, thres = 0): number[] {
105110
//helpers -getmedian
106111
export function analyseRow(arr: Array<number | undefined>): { pre: QuickStats; post: QuickStats } {
107112
const cleaned = cleanRow(arr);
108-
// which data to consider;
109-
const [pre, post] = createStatistics(cleaned);
110113

114+
// if we only have one datapoint, no need for statistics.
115+
if (cleaned.length == 1) {
116+
const p = {
117+
median: cleaned[0],
118+
stddev: -1,
119+
n: 1,
120+
};
121+
return {
122+
pre: p,
123+
post: p,
124+
};
125+
}
126+
127+
// which data to consider; i.e remove outliers
128+
const [pre, post] = createStatistics(cleaned);
111129
return {
112130
pre: {
113131
median: Stats.median(pre),

src/helper/argParse.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@ import {
3030
} from "@/bridge/fiat-bridge/constants";
3131
import { errorOut, ERRORS } from "@/errors";
3232

33-
import { FRAME_POINTER_OPTIONS, MEMORY_CONSTRAINTS_OPTIONS, ParsedArgsT } from "../types";
33+
import {
34+
FRAME_POINTER_OPTIONS,
35+
UICA_OPTIONS,
36+
MEMORY_CONSTRAINTS_OPTIONS,
37+
ParsedArgsT,
38+
OBJECTIVE_FUNCTION_OPTIONS,
39+
} from "../types";
3440

3541
const y = await yargs(process.argv.slice(2));
3642

@@ -216,6 +222,21 @@ export const parsedArgs = y
216222
"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)))",
217223
choices: MEMORY_CONSTRAINTS_OPTIONS,
218224
})
225+
.option("uicaarch", {
226+
default: "SKL",
227+
string: true,
228+
describe:
229+
"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.",
230+
choices: UICA_OPTIONS,
231+
})
232+
.option("objectiveFunction", {
233+
default: "cycles",
234+
string: true,
235+
describe:
236+
"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.",
237+
238+
choices: OBJECTIVE_FUNCTION_OPTIONS,
239+
})
219240
.help("help")
220241
.alias("h", "help")
221242
.wrap(Math.min(160, y.terminalWidth()))

src/helper/globals.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type { CryptoptGlobals } from "@/types";
1919
const globals: CryptoptGlobals = {
2020
currentRatio: Infinity,
2121
convergence: [] as string[], // numbers, but .toFixed(4)
22+
convergenceMetric: [] as string[], // see interface
2223
time: {
2324
// in seconds
2425
validate: 0,

0 commit comments

Comments
 (0)