@factoidal/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +121 -0
- package/LICENSE +201 -0
- package/README.md +514 -0
- package/browser-wasm.js +872 -0
- package/browser.d.ts +514 -0
- package/browser.js +2276 -0
- package/factoidal-npm-entry.js +32609 -0
- package/factoidal-npm-entry.wasm.assets/code-7ac046580f1bbdda8dc6.wasm +0 -0
- package/factoidal-npm-entry.wasm.js +455 -0
- package/factoidal.js +27560 -0
- package/factoidal.wasm.assets/code-bbe6099bfb5b10c4c3ab.wasm +0 -0
- package/factoidal.wasm.js +457 -0
- package/fn.d.ts +519 -0
- package/fn.js +916 -0
- package/hacl-init.js +92 -0
- package/hacl-wasm/FStar.wasm +0 -0
- package/hacl-wasm/Hacl_Bignum.wasm +0 -0
- package/hacl-wasm/Hacl_Bignum25519_51.wasm +0 -0
- package/hacl-wasm/Hacl_Bignum_Base.wasm +0 -0
- package/hacl-wasm/Hacl_Curve25519_51.wasm +0 -0
- package/hacl-wasm/Hacl_Ed25519.wasm +0 -0
- package/hacl-wasm/Hacl_Ed25519_PrecompTable.wasm +0 -0
- package/hacl-wasm/Hacl_Hash_Base.wasm +0 -0
- package/hacl-wasm/Hacl_Hash_SHA2.wasm +0 -0
- package/hacl-wasm/Hacl_IntTypes_Intrinsics.wasm +0 -0
- package/hacl-wasm/LowStar_Endianness.wasm +0 -0
- package/hacl-wasm/WasmSupport.wasm +0 -0
- package/hacl-wasm/api.js +775 -0
- package/hacl-wasm/api.json +3787 -0
- package/hacl-wasm/layouts.json +1 -0
- package/hacl-wasm/loader.js +568 -0
- package/hacl-wasm/shell.js +12 -0
- package/index.d.ts +1068 -0
- package/index.js +237 -0
- package/index.mjs +85 -0
- package/lib/api.js +2140 -0
- package/lib/engine-js.js +165 -0
- package/lib/engine-wasm.js +300 -0
- package/package.json +101 -0
- package/rdfjs.js +540 -0
- package/version.json +101 -0
- package/wasm.d.ts +130 -0
- package/wasm.js +158 -0
package/hacl-wasm/api.js
ADDED
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
// jshint esversion: 6
|
|
2
|
+
|
|
3
|
+
if (typeof module !== 'undefined') {
|
|
4
|
+
var fs = require('fs');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var loader = require(path.resolve(__dirname, './loader.js'));
|
|
7
|
+
var shell = require(path.resolve(__dirname, './shell.js'));
|
|
8
|
+
var api_promise = Promise.resolve(require(path.resolve(__dirname, './api.json')));
|
|
9
|
+
var layouts_promise = Promise.resolve(require(path.resolve(__dirname, './layouts.json')));
|
|
10
|
+
|
|
11
|
+
} else {
|
|
12
|
+
var loader = this;
|
|
13
|
+
var shell = this;
|
|
14
|
+
var api_promise = fetch("api.json").then(r => r.json());
|
|
15
|
+
var layouts_promise = fetch("layouts.json").then(r => r.json());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// We now allow the user to pass a custom list of modules if they want to do
|
|
19
|
+
// their own, more lightweight packaging.
|
|
20
|
+
function getModulesPromise(modules=shell.my_modules) {
|
|
21
|
+
const readModule = async m =>
|
|
22
|
+
typeof module !== 'undefined'
|
|
23
|
+
? new Uint8Array(await fs.promises.readFile(path.resolve(__dirname, './' + m)))
|
|
24
|
+
: (await fetch(m)).arrayBuffer();
|
|
25
|
+
return Promise.all(modules.map(async name =>
|
|
26
|
+
({ buf: await readModule(name + ".wasm"), name })
|
|
27
|
+
));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Comment out for debug
|
|
31
|
+
loader.setMyPrint((x) => {});
|
|
32
|
+
|
|
33
|
+
// HELPERS FOR SIZE FIELDS
|
|
34
|
+
// -----------------------
|
|
35
|
+
|
|
36
|
+
// Grammar of size fields:
|
|
37
|
+
//
|
|
38
|
+
// f ::=
|
|
39
|
+
// var
|
|
40
|
+
// var<OP>n
|
|
41
|
+
// M.f(var)
|
|
42
|
+
//
|
|
43
|
+
// var: variable, one of the length variables, may be ghost
|
|
44
|
+
// n: integer constant
|
|
45
|
+
// <OP>: +, -, * or /
|
|
46
|
+
// M.f: function, referred to by its name in the high-level API, e.g.
|
|
47
|
+
// EverCrypt_Hash.hash_len (and not EverCrypt_Hash_Incremental_hash_len)
|
|
48
|
+
|
|
49
|
+
var parseOp = (arg, op) => {
|
|
50
|
+
let [ var_, const_ ] = arg.split(op);
|
|
51
|
+
return [ "Op", var_, op, parseInt(const_) ];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
var parseApp = (arg) => {
|
|
55
|
+
let i = arg.indexOf("(");
|
|
56
|
+
let [ m, f ] = arg.substr(0, i).split(".");
|
|
57
|
+
let x = arg.substr(i+1, arg.length - 1 - i - 1);
|
|
58
|
+
return [ "App", m, f, parseSize(x) ];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
var parseSize = (arg) => {
|
|
62
|
+
if (arg.includes("+"))
|
|
63
|
+
return parseOp(arg, "+");
|
|
64
|
+
if (arg.includes("-"))
|
|
65
|
+
return parseOp(arg, "-");
|
|
66
|
+
if (arg.includes("*"))
|
|
67
|
+
return parseOp(arg, "*");
|
|
68
|
+
if (arg.includes("/"))
|
|
69
|
+
return parseOp(arg, "/");
|
|
70
|
+
|
|
71
|
+
if (arg.includes("("))
|
|
72
|
+
return parseApp(arg);
|
|
73
|
+
|
|
74
|
+
return [ "Var", arg ];
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
var evalSize = function(parsedSize, args_int32s, api) {
|
|
78
|
+
let [ kind, ...args ] = parsedSize;
|
|
79
|
+
switch (kind) {
|
|
80
|
+
case "Var": {
|
|
81
|
+
let [ var_ ] = args;
|
|
82
|
+
return args_int32s[var_];
|
|
83
|
+
}
|
|
84
|
+
case "Op": {
|
|
85
|
+
let [ var_, op, const_ ] = args;
|
|
86
|
+
switch (op) {
|
|
87
|
+
case "+":
|
|
88
|
+
return args_int32s[var_] + const_;
|
|
89
|
+
case "-":
|
|
90
|
+
return args_int32s[var_] - const_;
|
|
91
|
+
case "*":
|
|
92
|
+
return args_int32s[var_] * const_;
|
|
93
|
+
case "/":
|
|
94
|
+
if (args_int32s[var_] % const_ != 0)
|
|
95
|
+
throw new Error("Argument whose length is "+arg+" is not a multiple of "+const_);
|
|
96
|
+
return args_int32s[var_] / const_;
|
|
97
|
+
default:
|
|
98
|
+
throw new Error("Illegal operator: "+op);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
case "App": {
|
|
102
|
+
let [ m, f, x ] = args;
|
|
103
|
+
if (!(m in api))
|
|
104
|
+
throw new Error("For size "+parsedSize+", module "+m+" is unknown");
|
|
105
|
+
if (!(f in api[m]))
|
|
106
|
+
throw new Error("For size "+parsedSize+", function "+m+"."+f+" is unknown");
|
|
107
|
+
// console.log("RECURSIVE EVAL SIZE ENTER: ", x);
|
|
108
|
+
x = evalSize(x, args_int32s, api);
|
|
109
|
+
// console.log("RECURSIVE EVAL SIZE END: ", x);
|
|
110
|
+
return api[m][f](x)[0];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
// Given a size formula `var<OP>n` = `total`, invert it, i.e. solve the
|
|
117
|
+
// equation where `var` is unknown.
|
|
118
|
+
//
|
|
119
|
+
// @param {String} arg The formula of the form var<OP>n
|
|
120
|
+
// @param {Number} total The value of the formula
|
|
121
|
+
// @return {[String, Number]} The variable name `var` and its value.
|
|
122
|
+
var invertSize = function(parsedSize, total) {
|
|
123
|
+
let [ kind, var_, op, const_ ] = parsedSize;
|
|
124
|
+
if (kind != "Var" && kind != "Op")
|
|
125
|
+
throw new Error("Illegal size for an input: "+parsedSize);
|
|
126
|
+
switch (op) {
|
|
127
|
+
case "+":
|
|
128
|
+
return [ var_, total - parseInt(const_) ];
|
|
129
|
+
case "-":
|
|
130
|
+
return [ var_, total + parseInt(const_) ];
|
|
131
|
+
case "*":
|
|
132
|
+
let x = parseInt(const_);
|
|
133
|
+
if (total % x != 0)
|
|
134
|
+
throw new Error("Argument whose length is "+parsedSize+" is not a multiple of "+x);
|
|
135
|
+
return [ var_, total / x ];
|
|
136
|
+
case "/":
|
|
137
|
+
return [ var_, total * parseInt(const_) ];
|
|
138
|
+
default:
|
|
139
|
+
return [var_, total];
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// VALIDATION
|
|
144
|
+
// ----------
|
|
145
|
+
|
|
146
|
+
// The following function validates the contents of `api.json`. It is meant as
|
|
147
|
+
// a helper when creating new binders, it provides explicit error messages.
|
|
148
|
+
// It also has the side effect of filling out the parsedSize field on `arg`
|
|
149
|
+
// objects, so that we don't needlessly re-parse every size field all the time.
|
|
150
|
+
var validateJSON = function(json) {
|
|
151
|
+
for (let key_module in json) {
|
|
152
|
+
for (let key_func in json[key_module]) {
|
|
153
|
+
let func_obj = json[key_module][key_func];
|
|
154
|
+
let obj_name = key_module + "." + key_func;
|
|
155
|
+
|
|
156
|
+
if (!("module" in func_obj))
|
|
157
|
+
throw Error("please provide a 'module' field for " + obj_name + " in api.json");
|
|
158
|
+
if (!(shell.my_modules.includes(func_obj.module)))
|
|
159
|
+
throw Error(obj_name + ".module='" + func_obj.module + "' of api.json should be listed in shell.js");
|
|
160
|
+
if (!("name" in func_obj))
|
|
161
|
+
throw Error("please provide a 'name' field for " + obj_name + " in api.json");
|
|
162
|
+
if (!("args" in func_obj))
|
|
163
|
+
throw Error("please provide a 'args' field for " + obj_name + " in api.json");
|
|
164
|
+
if (!Array.isArray(func_obj.args))
|
|
165
|
+
throw Error("the 'args' field for " + obj_name + " should be an array");
|
|
166
|
+
|
|
167
|
+
let length_args_available = {};
|
|
168
|
+
func_obj.args.forEach((arg, i) => {
|
|
169
|
+
if (!(arg.kind === "input" || (arg.kind === "output")))
|
|
170
|
+
throw Error("in " + obj_name + ", argument #" + i + " should have a 'kind' that is 'output' or 'input'");
|
|
171
|
+
if (!(arg.type === "bool" || arg.type === "uint32" || arg.type.startsWith("buffer") || arg.type[0].toUpperCase() == arg.type[0]))
|
|
172
|
+
throw Error("in " + obj_name + ", argument #" + i + " should have a 'kind' that is 'int', 'bool' or 'buffer'");
|
|
173
|
+
if (arg.type.startsWith("buffer") && arg.size === undefined)
|
|
174
|
+
throw Error("in " + obj_name + ", argument #" + i + " is a buffer and should have a 'size' field");
|
|
175
|
+
if (arg.kind === "input" && arg.type.startsWith("buffer") && !("interface_index" in arg))
|
|
176
|
+
throw Error("in " + obj_name + ", argument #" + i + " is an input and should have a 'interface_index' field");
|
|
177
|
+
if ((arg.kind === "output" || (arg.kind === "input" && arg.interface_index !== undefined)) && arg.tests === undefined)
|
|
178
|
+
throw Error("please provide a 'tests' field for argument #" + i + " of " + obj_name + " in api.json");
|
|
179
|
+
if ((arg.kind === "output" || (arg.kind === "input" && arg.interface_index !== undefined)) && !Array.isArray(arg.tests))
|
|
180
|
+
throw Error("the 'tests' field for argument #" + i + " of " + obj_name + " should be an array");
|
|
181
|
+
|
|
182
|
+
if (arg.type === "uint32" && arg.kind === "input" && arg.interface_index !== undefined)
|
|
183
|
+
length_args_available[arg.name] = true;
|
|
184
|
+
|
|
185
|
+
if (arg.type.startsWith("buffer") && arg.kind === "input" && typeof arg.size === "string") {
|
|
186
|
+
arg.parsedSize = parseSize(arg.size);
|
|
187
|
+
let [ kind, var_ ] = arg.parsedSize;
|
|
188
|
+
if (kind == "Var" || kind == "Op")
|
|
189
|
+
length_args_available[var_] = true;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
func_obj.args.forEach(function(arg, i) {
|
|
193
|
+
if (arg.type.startsWith("buffer") && typeof arg.size === "string" && arg.kind === "output") {
|
|
194
|
+
arg.parsedSize = parseSize(arg.size);
|
|
195
|
+
let [ kind, var_ ] = arg.parsedSize;
|
|
196
|
+
if ((kind == "Var" || kind == "Op") && !(var_ in length_args_available)) {
|
|
197
|
+
console.log(arg);
|
|
198
|
+
console.log(length_args_available);
|
|
199
|
+
throw Error("incorrect 'size' field value (" + arg.size + ") for argument #" + i + " of " + obj_name + " in api.json");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
if (func_obj.return === undefined) {
|
|
204
|
+
throw Error("please provide a 'return' field for " + obj_name + " in api.json");
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// The module is encapsulated inside a closure to prevent anybody from accessing
|
|
211
|
+
// the WebAssembly memory.
|
|
212
|
+
var HaclWasm = (function() {
|
|
213
|
+
'use strict';
|
|
214
|
+
var isInitialized = false;
|
|
215
|
+
var Module = {};
|
|
216
|
+
|
|
217
|
+
// We defined a few WASM-specific "compile-time macros".
|
|
218
|
+
var my_imports = {
|
|
219
|
+
EverCrypt_TargetConfig: (mem) => ({
|
|
220
|
+
hacl_can_compile_vale: 0,
|
|
221
|
+
hacl_can_compile_vec128: 0,
|
|
222
|
+
hacl_can_compile_vec256: 0,
|
|
223
|
+
has_vec128_not_avx: () => false,
|
|
224
|
+
has_vec256_not_avx2: () => false,
|
|
225
|
+
}),
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// The WebAssembly modules have to be initialized before calling any function.
|
|
229
|
+
// To be called only if isInitialized == false.
|
|
230
|
+
var loadWasm = async (modules) => {
|
|
231
|
+
if (!isInitialized) {
|
|
232
|
+
Module = await loader.link(
|
|
233
|
+
my_imports,
|
|
234
|
+
await getModulesPromise(modules)
|
|
235
|
+
);
|
|
236
|
+
isInitialized = true;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/*
|
|
241
|
+
Inside WebAssembly, the functions only take pointers to memory and integers.
|
|
242
|
+
However, we want to expose the functions of the wasm module with a nice Javascript
|
|
243
|
+
API that manipulates ArrayBuffers.
|
|
244
|
+
|
|
245
|
+
In order to do that, we have to describe the Javascript prototype of each function
|
|
246
|
+
that we expose. The functions can take and return multiple objects that can be
|
|
247
|
+
buffers, integers or booleans. The buffers can either have a fixed length (and
|
|
248
|
+
in that case we check dynamically whether they have the correct length), or
|
|
249
|
+
have a variable length (and we have to pass that length as an additional
|
|
250
|
+
parameter to WebAssembly).
|
|
251
|
+
|
|
252
|
+
In order to match the Javascript API with the actual calls to WebAssembly functions,
|
|
253
|
+
we have to describe the correspondence between the two in the `api.json` file.
|
|
254
|
+
|
|
255
|
+
The scheme of the JSON file is the following :
|
|
256
|
+
- `module`, this module name will be shown in the JS API
|
|
257
|
+
- `function`, this function name will be shown in the JS API
|
|
258
|
+
- `module`, the name of the WebAssembly file where to find the function
|
|
259
|
+
- 'name', the name of the WebAssembly export corresponding to the function
|
|
260
|
+
- 'args', the list of the WebAssembly arguments expected by the function
|
|
261
|
+
- 'name', the name of the argument which will be shown in the JS Doc
|
|
262
|
+
- 'kind', either `input` or `output` of the function
|
|
263
|
+
- 'type', either 'int', 'boolean', 'buffer', 'buffer(uint32)',
|
|
264
|
+
'buffer(uint64)', or the name of a struct starting with an uppercase;
|
|
265
|
+
for the latter case, this is understood to be a pointer (the WASM API
|
|
266
|
+
does not take structs by value), and we assume the type is described
|
|
267
|
+
in layouts.json -- in that case, kind is implicitly assumed to be
|
|
268
|
+
input, and kind == "output" is understood to mean input-output
|
|
269
|
+
- 'size', see grammar of size fields above
|
|
270
|
+
- 'interface_index', for all `input` that should appear in JS, position
|
|
271
|
+
inside the argument list of the JS function
|
|
272
|
+
- 'tests', a list of values for this arguments, each value corresponding
|
|
273
|
+
to a different test case
|
|
274
|
+
- 'return', the return type of the WebAssembly function
|
|
275
|
+
- 'custom_module_name', if true, it signifies that the prefix of the name
|
|
276
|
+
of the WebAssembly function does not coincide with the name of the
|
|
277
|
+
WebAssembly module; the module name will not be used when calling it,
|
|
278
|
+
instead 'name' will contain the full name of the function
|
|
279
|
+
*/
|
|
280
|
+
|
|
281
|
+
var array_type = function(type) {
|
|
282
|
+
switch (type) {
|
|
283
|
+
case "buffer":
|
|
284
|
+
return Uint8Array;
|
|
285
|
+
case "buffer(uint32)":
|
|
286
|
+
return Uint32Array;
|
|
287
|
+
case "buffer(uint64)":
|
|
288
|
+
return BigUint64Array;
|
|
289
|
+
default:
|
|
290
|
+
throw new Error("Unknown array type: "+type);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
var cell_size = type => array_type(type).BYTES_PER_ELEMENT;
|
|
295
|
+
|
|
296
|
+
var check_array_type = function(type, candidate, length, name) {
|
|
297
|
+
if (!(candidate instanceof array_type(type)) || candidate.length !== length) {
|
|
298
|
+
throw new Error(
|
|
299
|
+
"name: Please ensure the argument " + name + " has length " + length + " and is a " + array_type(type)
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
var copy_array_to_stack = function(type, array, i) {
|
|
305
|
+
// This returns a suitably-aligned pointer.
|
|
306
|
+
var pointer = loader.reserve(Module.Karamel.mem, array.length*cell_size(type), cell_size(type));
|
|
307
|
+
(new Uint8Array(Module.Karamel.mem.buffer)).set(new Uint8Array(array.buffer), pointer);
|
|
308
|
+
// console.log("argument "+i, "stack pointer got", loader.p32(pointer));
|
|
309
|
+
// console.log(array, array.length);
|
|
310
|
+
// console.log("source", array.buffer);
|
|
311
|
+
// loader.dump(Module.Karamel.mem, 2048, 0x13000);
|
|
312
|
+
return pointer;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// len is in number of elements
|
|
316
|
+
var read_memory = function(type, ptr, len) {
|
|
317
|
+
// TODO: faster path with aligned pointers?
|
|
318
|
+
var result = new ArrayBuffer(len*cell_size(type));
|
|
319
|
+
// console.log("New memory buffer", type, len, len*cell_size(type));
|
|
320
|
+
(new Uint8Array(result).set(new Uint8Array(Module.Karamel.mem.buffer)
|
|
321
|
+
.subarray(ptr, ptr + len*cell_size(type))));
|
|
322
|
+
// console.log(result);
|
|
323
|
+
return new (array_type(type))(result);
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// HELPERS FOR HEAP LAYOUT
|
|
327
|
+
// -----------------------
|
|
328
|
+
|
|
329
|
+
// Filled out via a promise.
|
|
330
|
+
var layouts;
|
|
331
|
+
|
|
332
|
+
var heapReadBlockSize = (ptr) => {
|
|
333
|
+
var m32 = new Uint32Array(Module.Karamel.mem.buffer)
|
|
334
|
+
return m32[ptr/4-2]-8;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// We adopt a uniform layout and length-tag buffers upon copying them onto the
|
|
338
|
+
// stack. This allows reading back layouts safely after they're modified.
|
|
339
|
+
var heapWriteBlockSize = (ptr, sz) => {
|
|
340
|
+
var m32 = new Uint32Array(Module.Karamel.mem.buffer)
|
|
341
|
+
return m32[ptr/4-2] = sz + 8;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
var heapReadBuffer = (type, ptr) => {
|
|
345
|
+
// Pointer points to the actual data, header is 8 bytes before, length in
|
|
346
|
+
// header includes header. Heap base pointers are always aligned on 8 byte
|
|
347
|
+
// boundaries, but inner pointers (e.g. within a struct) are aligned on
|
|
348
|
+
// their size.
|
|
349
|
+
if (!((ptr % cell_size(type)) == 0))
|
|
350
|
+
throw new Error("malloc violation (1)");
|
|
351
|
+
let block_size = heapReadBlockSize(ptr);
|
|
352
|
+
if (!(block_size % cell_size(type) == 0))
|
|
353
|
+
throw new Error("malloc violation (2)");
|
|
354
|
+
let len = block_size / cell_size(type);
|
|
355
|
+
// console.log("pointer:", loader.p32(ptr), "header:", loader.p32(ptr-8), "len:", loader.p32(len));
|
|
356
|
+
return read_memory(type, ptr, len);
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
var heapReadInt = (typ, ptr) => {
|
|
360
|
+
switch (typ) {
|
|
361
|
+
case "A8":
|
|
362
|
+
var m8 = new Uint8Array(Module.Karamel.mem.buffer);
|
|
363
|
+
return m8[ptr];
|
|
364
|
+
|
|
365
|
+
case "A32":
|
|
366
|
+
if (!(ptr % 4) == 0)
|
|
367
|
+
throw new Error("malloc violation (3)");
|
|
368
|
+
var m32 = new Uint32Array(Module.Karamel.mem.buffer);
|
|
369
|
+
return m32[ptr/4];
|
|
370
|
+
|
|
371
|
+
case "A64":
|
|
372
|
+
if (!(ptr % 8) == 0)
|
|
373
|
+
throw new Error("malloc violation (4)");
|
|
374
|
+
var m64 = new BigUint64Array(Module.Karamel.mem.buffer);
|
|
375
|
+
return m64[ptr/8];
|
|
376
|
+
|
|
377
|
+
default:
|
|
378
|
+
throw new Error("Not implemented: "+typ);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
var heapWriteInt = (typ, ptr, v) => {
|
|
383
|
+
switch (typ) {
|
|
384
|
+
case "A8":
|
|
385
|
+
var m8 = new Uint8Array(Module.Karamel.mem.buffer);
|
|
386
|
+
m8[ptr] = v;
|
|
387
|
+
break;
|
|
388
|
+
|
|
389
|
+
case "A32":
|
|
390
|
+
if (!(ptr % 4) == 0)
|
|
391
|
+
throw new Error("malloc violation (3)");
|
|
392
|
+
var m32 = new Uint32Array(Module.Karamel.mem.buffer);
|
|
393
|
+
m32[ptr/4] = v;
|
|
394
|
+
break;
|
|
395
|
+
|
|
396
|
+
case "A64":
|
|
397
|
+
if (!(ptr % 8) == 0)
|
|
398
|
+
throw new Error("malloc violation (4)");
|
|
399
|
+
var m64 = new BigUint64Array(Module.Karamel.mem.buffer);
|
|
400
|
+
m64[ptr/8] = v;
|
|
401
|
+
break;
|
|
402
|
+
|
|
403
|
+
default:
|
|
404
|
+
throw new Error("Not implemented: "+typ);
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// Fast-path for arrays of flat integers.
|
|
409
|
+
var heapReadBlockFast = (int_type, ptr) => {
|
|
410
|
+
switch (int_type) {
|
|
411
|
+
case "A8":
|
|
412
|
+
return heapReadBuffer("buffer", ptr);
|
|
413
|
+
case "A32":
|
|
414
|
+
return heapReadBuffer("buffer(uint32)", ptr);
|
|
415
|
+
case "A64":
|
|
416
|
+
return heapReadBuffer("buffer(uint64)", ptr);
|
|
417
|
+
default:
|
|
418
|
+
throw new Error("Not implemented: "+int_type);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// Fast-path for arrays of flat integers.
|
|
423
|
+
var heapWriteBlockFast = (int_type, ptr, arr) => {
|
|
424
|
+
// console.log(arr);
|
|
425
|
+
(new Uint8Array(Module.Karamel.mem.buffer)).set(new Uint8Array(arr.buffer), ptr);
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// Eventually will be mutually recursive once Layout is implemented (flat
|
|
429
|
+
// packed structs).
|
|
430
|
+
var heapReadType = (runtime_type, ptr) => {
|
|
431
|
+
// console.log("headReadType", runtime_type, loader.p32(ptr));
|
|
432
|
+
let [ type, data ] = runtime_type;
|
|
433
|
+
switch (type) {
|
|
434
|
+
case "Int":
|
|
435
|
+
return heapReadInt(data[0], ptr);
|
|
436
|
+
case "Pointer":
|
|
437
|
+
if (data[0] == "Int")
|
|
438
|
+
return heapReadBlockFast(data[1][0], heapReadInt("A32", ptr));
|
|
439
|
+
else if (data[0] == "Layout")
|
|
440
|
+
return heapReadLayout(data[1], heapReadInt("A32", ptr));
|
|
441
|
+
// pass-through
|
|
442
|
+
default:
|
|
443
|
+
throw new Error("Not implemented: "+type+","+data);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
var heapWriteType = (runtime_type, ptr, v) => {
|
|
448
|
+
// console.log("heapWriteType", runtime_type, loader.p32(ptr), v);
|
|
449
|
+
let [ type, data ] = runtime_type;
|
|
450
|
+
switch (type) {
|
|
451
|
+
case "Int":
|
|
452
|
+
heapWriteInt(data[0], ptr, v);
|
|
453
|
+
break;
|
|
454
|
+
case "Pointer":
|
|
455
|
+
if (data[0] == "Int") {
|
|
456
|
+
let sz = v.buffer.byteLength;
|
|
457
|
+
// NB: could be more precise with alignment, I guess
|
|
458
|
+
let dst = loader.reserve(Module.Karamel.mem, sz + 8, 8) + 8;
|
|
459
|
+
heapWriteInt("A32", ptr, dst);
|
|
460
|
+
heapWriteBlockFast(data[1][0], dst, v);
|
|
461
|
+
heapWriteBlockSize(dst, sz);
|
|
462
|
+
break;
|
|
463
|
+
} else if (data[0] == "Layout") {
|
|
464
|
+
let dst = stackWriteLayout(data[1], v);
|
|
465
|
+
heapWriteInt("A32", ptr, dst);
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
// pass-through
|
|
469
|
+
default:
|
|
470
|
+
throw new Error("Not implemented: "+type);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
var lFlatIsTaggedUnion = data => {
|
|
475
|
+
if (data.fields.length == 2 && data.fields[0][0] == "tag" && data.fields[1][0] == "val") {
|
|
476
|
+
let [ tag_name, [ tag_ofs, [ tag_type, [ tag_width ]]]] = data.fields[0];
|
|
477
|
+
if (!(tag_name == "tag" && tag_ofs == "0" && tag_type == "Int" && tag_width == "A32"))
|
|
478
|
+
throw new Error("Inconsistent tag");
|
|
479
|
+
let [ val_name, [ val_ofs, [ val_type, val_cases]]] = data.fields[1];
|
|
480
|
+
if (!(val_name == "val" && val_ofs == "8" && val_type == "Union"))
|
|
481
|
+
throw new Error("Inconsistent val");
|
|
482
|
+
return true;
|
|
483
|
+
} else {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
var taggedUnionGetCase = (data, tag) => {
|
|
489
|
+
let [ val_name, [ val_ofs, [ val_type, val_cases]]] = data.fields[1];
|
|
490
|
+
return val_cases[tag];
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
var heapReadLayout = (layout, ptr) => {
|
|
494
|
+
// console.log("heapReadLayout", layout, loader.p32(ptr));
|
|
495
|
+
let [ tag, data ] = layouts[layout];
|
|
496
|
+
switch (tag) {
|
|
497
|
+
case "LFlat":
|
|
498
|
+
if (lFlatIsTaggedUnion(data)) {
|
|
499
|
+
let tag = heapReadInt("A32", ptr);
|
|
500
|
+
return ({
|
|
501
|
+
tag,
|
|
502
|
+
val: heapReadType(taggedUnionGetCase(data, tag), ptr + 8)
|
|
503
|
+
});
|
|
504
|
+
} else {
|
|
505
|
+
let o = {};
|
|
506
|
+
data.fields.forEach(([field, [ ofs, typ ]]) =>
|
|
507
|
+
o[field] = heapReadType(typ, ptr + ofs)
|
|
508
|
+
);
|
|
509
|
+
return o;
|
|
510
|
+
}
|
|
511
|
+
default:
|
|
512
|
+
throw new Error("Not implemented: "+tag);
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
var heapWriteLayout = (layout, ptr, v) => {
|
|
517
|
+
let [ tag, data ] = layouts[layout];
|
|
518
|
+
// console.log(v);
|
|
519
|
+
switch (tag) {
|
|
520
|
+
case "LFlat":
|
|
521
|
+
if (lFlatIsTaggedUnion(data)) {
|
|
522
|
+
heapWriteInt("A32", ptr, v.tag);
|
|
523
|
+
heapWriteType(taggedUnionGetCase(data, v.tag), ptr + 8, v.val);
|
|
524
|
+
} else {
|
|
525
|
+
data.fields.forEach(([field, [ ofs, typ ]]) => {
|
|
526
|
+
// console.log("Writing", v[field]);
|
|
527
|
+
heapWriteType(typ, ptr + ofs, v[field]);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
break;
|
|
531
|
+
default:
|
|
532
|
+
throw new Error("Not implemented: "+tag);
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
var stackWriteLayout = (layout, v) => {
|
|
537
|
+
// console.log("stackWriteLayout", layout, v);
|
|
538
|
+
let [ tag, data ] = layouts[layout];
|
|
539
|
+
switch (tag) {
|
|
540
|
+
case "LFlat":
|
|
541
|
+
let ptr = loader.reserve(Module.Karamel.mem, data.size, 8);
|
|
542
|
+
heapWriteLayout(layout, ptr, v);
|
|
543
|
+
return ptr;
|
|
544
|
+
default:
|
|
545
|
+
throw new Error("Not implemented: "+tag);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
// END HELPERS FOR HEAP LAYOUT
|
|
550
|
+
|
|
551
|
+
// The object being filled:
|
|
552
|
+
// - first level of keys = modules,
|
|
553
|
+
// - second level of keys = functions within a module
|
|
554
|
+
var api_obj = {};
|
|
555
|
+
|
|
556
|
+
// This is the main logic; this function is partially applied to its
|
|
557
|
+
// first two arguments for each API entry. We assume JITs are working well
|
|
558
|
+
// enough to make this efficient.
|
|
559
|
+
var callWithProto = function(proto, args) {
|
|
560
|
+
var expected_args_number = proto.args.filter(function(arg) {
|
|
561
|
+
return arg.interface_index !== undefined;
|
|
562
|
+
}).length;
|
|
563
|
+
if (args.length != expected_args_number) {
|
|
564
|
+
throw Error("wrong number of arguments to call the F*-wasm function " + proto.name + ": expected " + expected_args_number + ", got " + args.length);
|
|
565
|
+
}
|
|
566
|
+
var memory = new Uint32Array(Module.Karamel.mem.buffer);
|
|
567
|
+
var sp = memory[0];
|
|
568
|
+
|
|
569
|
+
// Integer arguments are either
|
|
570
|
+
// - user-provided, in which case they have an interface_index
|
|
571
|
+
// - automatically determined, in which case they appear in the `size` field
|
|
572
|
+
// of another buffer argument.
|
|
573
|
+
// In a first pass, we need to figure out the value of all integer
|
|
574
|
+
// arguments to enable lookups by name.
|
|
575
|
+
var args_int32s = {};
|
|
576
|
+
proto.args.forEach(function(arg) {
|
|
577
|
+
if (arg.type.startsWith("buffer") && typeof arg.size === "string" && arg.interface_index !== undefined && arg.kind === "input") {
|
|
578
|
+
// API contains e.g.:
|
|
579
|
+
// { "name": "len" },
|
|
580
|
+
// { "name": "buf", "type": "buffer", "size": "len", "interface_index": 3, "kind": "input" }
|
|
581
|
+
// We need to figure out `len` automatically since it doesn't have an
|
|
582
|
+
// interface index, meaning it isn't one of the arguments passed to the
|
|
583
|
+
// high-level API. We know `buf` is the second argument passed to the
|
|
584
|
+
// function, and thus allows us to fill out `len`.
|
|
585
|
+
let [ var_, var_value ] = invertSize(arg.parsedSize, args[arg.interface_index].length);
|
|
586
|
+
// console.log("Determined "+var_+"="+var_value);
|
|
587
|
+
|
|
588
|
+
if (var_ in args_int32s && var_value != args_int32s[var_])
|
|
589
|
+
throw new Error("Inconsistency in sizes; previously, "+var_+"="+args_int32s[var_]+"; now "+var_value);
|
|
590
|
+
args_int32s[var_] = var_value;
|
|
591
|
+
} else if (arg.interface_index !== undefined) {
|
|
592
|
+
// API contains e.g.:
|
|
593
|
+
// { "name": "len", "interface_index": 3 },
|
|
594
|
+
// { "name": "buf", "type": "buffer", "size": "len", "kind": "output" }
|
|
595
|
+
// We know we will need `len` below when trying to allocate an array for
|
|
596
|
+
// `output` -- insert it into the table.
|
|
597
|
+
// Note: we are quite lax and don't require that a length be a uint32,
|
|
598
|
+
// it's sometimes useful to allow it to be anything, like an address.
|
|
599
|
+
args_int32s[arg.name] = args[arg.interface_index];
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// We have determined the value of all user-provided and synthesized
|
|
604
|
+
// (computed) integer lengths. Now what happens with lengths is:
|
|
605
|
+
// - when allocating a variable-length output buffer, we compute the
|
|
606
|
+
// corresponding size via evalSize, passing it the args_int32s in case the
|
|
607
|
+
// size field of the output buffer refers to a variable
|
|
608
|
+
// - when computing the value of an integer argument that is not
|
|
609
|
+
// user-provided (i.e. has no interface_index), we look it up in
|
|
610
|
+
// args_int32s too
|
|
611
|
+
|
|
612
|
+
// This returns the effective arguments for the function call (all integers,
|
|
613
|
+
// some of which may be pointers on the stack). It has the side effect of
|
|
614
|
+
// growing the stack and copying the input buffers onto it.
|
|
615
|
+
var args = proto.args.map(function(arg, i) {
|
|
616
|
+
let debug = (type, x) => {
|
|
617
|
+
// console.log("Argument", i, type, loader.p32(x));
|
|
618
|
+
return x;
|
|
619
|
+
};
|
|
620
|
+
if (arg.type.startsWith("buffer")) {
|
|
621
|
+
var size;
|
|
622
|
+
if (typeof arg.size === "string") {
|
|
623
|
+
size = evalSize(arg.parsedSize, args_int32s, api_obj);
|
|
624
|
+
} else {
|
|
625
|
+
size = arg.size;
|
|
626
|
+
}
|
|
627
|
+
var arg_byte_buffer;
|
|
628
|
+
if (arg.kind === "input") {
|
|
629
|
+
var func_arg = args[arg.interface_index];
|
|
630
|
+
arg_byte_buffer = new (array_type(arg.type))(func_arg);
|
|
631
|
+
} else if (arg.kind === "output") {
|
|
632
|
+
arg_byte_buffer = new (array_type(arg.type))(size);
|
|
633
|
+
}
|
|
634
|
+
check_array_type(arg.type, arg_byte_buffer, size, arg.name);
|
|
635
|
+
// TODO: this copy is un-necessary in the case of output buffers.
|
|
636
|
+
return debug("array", copy_array_to_stack(arg.type, arg_byte_buffer, i));
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (arg.type === "bool" || arg.type === "uint32") {
|
|
640
|
+
if (arg.interface_index === undefined) {
|
|
641
|
+
// Variable-length argument, determined via first pass above.
|
|
642
|
+
return debug("int(auto)", args_int32s[arg.name]);
|
|
643
|
+
} else {
|
|
644
|
+
// Regular integer argument, passed by the user.
|
|
645
|
+
return debug("int", args[arg.interface_index]);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Layout... TODO: the "kind" field is unused because we do not have the
|
|
650
|
+
// case where the caller allocates empty space for a layout.
|
|
651
|
+
if (arg.type[0].toUpperCase() == arg.type[0]) {
|
|
652
|
+
let func_arg = args[arg.interface_index];
|
|
653
|
+
return debug("layout", stackWriteLayout(arg.type, func_arg));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
throw Error("Unimplemented ! ("+proto.name+")");
|
|
657
|
+
});
|
|
658
|
+
// console.log("Arguments laid out in WASM memory");
|
|
659
|
+
// args.forEach((arg, i) => console.log("argument "+i, loader.p32(arg)));
|
|
660
|
+
// loader.dump(Module.Karamel.mem, 2048, args[0] - (args[0] % 0x20));
|
|
661
|
+
|
|
662
|
+
// Calling the wasm function !
|
|
663
|
+
if (proto.custom_module_name) {
|
|
664
|
+
var func_name = proto.name;
|
|
665
|
+
} else {
|
|
666
|
+
var func_name = proto.module + "_" + proto.name;
|
|
667
|
+
}
|
|
668
|
+
if (!(proto.module in Module))
|
|
669
|
+
throw new Error(proto.module + " is not in Module");
|
|
670
|
+
if (!(func_name in Module[proto.module])) {
|
|
671
|
+
console.log(Object.keys(Module[proto.module]));
|
|
672
|
+
throw new Error(func_name + " is not in Module["+proto.module+"]");
|
|
673
|
+
}
|
|
674
|
+
var call_return = Module[proto.module][func_name](...args);
|
|
675
|
+
|
|
676
|
+
// console.log("After function call");
|
|
677
|
+
// loader.dump(Module.Karamel.mem, 256, args[0] - (args[0] % 0x20));
|
|
678
|
+
//loader.dump(Module.Karamel.mem, 256, call_return - (call_return % 0x20));
|
|
679
|
+
|
|
680
|
+
// Populating the JS buffers returned with their values read from Wasm memory
|
|
681
|
+
var return_buffers = args.map(function(pointer, i) {
|
|
682
|
+
let arg = proto.args[i];
|
|
683
|
+
if (arg.type[0].toUpperCase() == arg.type[0] && arg.kind == "output") {
|
|
684
|
+
// Layout
|
|
685
|
+
return heapReadLayout(arg.type, pointer);
|
|
686
|
+
} else if (arg.kind === "output") {
|
|
687
|
+
// Output buffer, allocated by us above, now need to read its contents
|
|
688
|
+
// out.
|
|
689
|
+
var size;
|
|
690
|
+
if (typeof arg.size === "string") {
|
|
691
|
+
size = evalSize(arg.parsedSize, args_int32s, api_obj);
|
|
692
|
+
} else {
|
|
693
|
+
size = arg.size;
|
|
694
|
+
}
|
|
695
|
+
// console.log("About to read", protoRet.type, loader.p32(pointer), size);
|
|
696
|
+
let r = read_memory(arg.type, pointer, size);
|
|
697
|
+
// console.log(r);
|
|
698
|
+
return r;
|
|
699
|
+
} else {
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
}).filter(v => v !== null);
|
|
703
|
+
|
|
704
|
+
// Resetting the stack pointer to its old value
|
|
705
|
+
memory[0] = sp;
|
|
706
|
+
if ("kind" in proto.return && proto.return.kind === "layout") {
|
|
707
|
+
// Heap-allocated value
|
|
708
|
+
let read = proto.return.type.startsWith("buffer") ? heapReadBuffer : heapReadLayout;
|
|
709
|
+
let r = read(proto.return.type, call_return);
|
|
710
|
+
memory[Module.Karamel.mem.buffer.byteLength/4-1] = 0;
|
|
711
|
+
return r;
|
|
712
|
+
|
|
713
|
+
// loader.dump(Module.Karamel.mem, 2048, Module.Karamel.mem.buffer.byteLength - 2048);
|
|
714
|
+
// console.log(loader.p32(call_return), r, JSON.stringify(layouts[proto.return.type], null, 2));
|
|
715
|
+
|
|
716
|
+
// let ptr = stackWriteLayout(proto.return.type, r);
|
|
717
|
+
// console.log(loader.p32(ptr));
|
|
718
|
+
// loader.dump(Module.Karamel.mem, 2048, 0x13000);
|
|
719
|
+
// throw new Error(func_name+": non-buffer return layout not implemented");
|
|
720
|
+
}
|
|
721
|
+
if (proto.return.type === "bool") {
|
|
722
|
+
return [call_return === 1, return_buffers].flat();
|
|
723
|
+
}
|
|
724
|
+
if (proto.return.type === "uint32") {
|
|
725
|
+
return [call_return >>> 0, return_buffers].flat();
|
|
726
|
+
}
|
|
727
|
+
if (proto.return.type === "uint64") {
|
|
728
|
+
// krml convention: uint64s are sent over as two uint32s
|
|
729
|
+
// console.log(call_return);
|
|
730
|
+
return [BigInt(call_return[0]>>>0) + (BigInt(call_return[1]>>>0) << 32n), return_buffers].flat();
|
|
731
|
+
}
|
|
732
|
+
if (proto.return.type === "void") {
|
|
733
|
+
return return_buffers;
|
|
734
|
+
}
|
|
735
|
+
throw new Error(func_name+": Unimplemented ! "+proto.return.type);
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
var getInitializedHaclModule = async function (modules) {
|
|
739
|
+
if (!isInitialized) {
|
|
740
|
+
// Load all WASM modules from network (web) or disk (node).
|
|
741
|
+
await loadWasm(modules);
|
|
742
|
+
// Write into the global.
|
|
743
|
+
layouts = await layouts_promise;
|
|
744
|
+
|
|
745
|
+
// Initial API validation (TODO: disable for release...?)
|
|
746
|
+
let api_json = await api_promise;
|
|
747
|
+
validateJSON(api_json);
|
|
748
|
+
|
|
749
|
+
// We follow the structure of api.json to expose an object whose structure
|
|
750
|
+
// follows the keys of api.json; each entry is a partial application of
|
|
751
|
+
// `callWithProto` (generic API wrapper) to its specific entry in api.json
|
|
752
|
+
// held in `api_json[key_module][key_func]`.
|
|
753
|
+
for (let key_module in api_json) {
|
|
754
|
+
for (let key_func in api_json[key_module]) {
|
|
755
|
+
if (api_obj[key_module] == null) {
|
|
756
|
+
api_obj[key_module] = {};
|
|
757
|
+
}
|
|
758
|
+
api_obj[key_module][key_func] = function(...args) {
|
|
759
|
+
return callWithProto(api_json[key_module][key_func], args);
|
|
760
|
+
};
|
|
761
|
+
};
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
return Promise.resolve(api_obj);
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
return {
|
|
768
|
+
getInitializedHaclModule: getInitializedHaclModule,
|
|
769
|
+
dump: (sz, ofs) => loader.dump(Module.Karamel.mem, sz, ofs)
|
|
770
|
+
};
|
|
771
|
+
})();
|
|
772
|
+
|
|
773
|
+
if (typeof module !== "undefined") {
|
|
774
|
+
module.exports = HaclWasm;
|
|
775
|
+
}
|