Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/src/utility.mjs
6165 views
1
/**
2
* @license
3
* Copyright 2010 The Emscripten Authors
4
* SPDX-License-Identifier: MIT
5
*/
6
7
// General JS utilities - things that might be useful in any JS project.
8
// Nothing specific to Emscripten appears here.
9
10
import * as url from 'node:url';
11
import * as path from 'node:path';
12
import * as fs from 'node:fs';
13
import * as vm from 'node:vm';
14
import assert from 'node:assert';
15
16
export function safeQuote(x) {
17
return x.replace(/"/g, '\\"').replace(/'/g, "\\'");
18
}
19
20
export function dump(item) {
21
let funcData;
22
try {
23
if (typeof item == 'object' && item != null && item.funcData) {
24
funcData = item.funcData;
25
item.funcData = null;
26
}
27
return '// ' + JSON.stringify(item, null, ' ').replace(/\n/g, '\n// ');
28
} catch {
29
const ret = [];
30
for (const [i, j] of Object.entries(item)) {
31
if (typeof j == 'string' || typeof j == 'number') {
32
ret.push(`${i}: ${j}`);
33
} else {
34
ret.push(`${i}: [?]`);
35
}
36
}
37
return ret.join(',\n');
38
} finally {
39
if (funcData) item.funcData = funcData;
40
}
41
}
42
43
let warnings = false;
44
45
export function warningOccured() {
46
return warnings;
47
}
48
49
let currentFile = [];
50
51
export function pushCurrentFile(f) {
52
currentFile.push(f);
53
}
54
55
export function popCurrentFile() {
56
currentFile.pop();
57
}
58
59
function errorPrefix(lineNo) {
60
if (!currentFile.length) return '';
61
const filename = currentFile[currentFile.length - 1];
62
if (lineNo) {
63
return `${filename}:${lineNo}: `;
64
} else {
65
return `${filename}: `;
66
}
67
}
68
69
export function warn(msg, lineNo) {
70
warnings = true;
71
printErr(`warning: ${errorPrefix(lineNo)}${msg}`);
72
}
73
74
const seenWarnings = new Set();
75
76
export function warnOnce(msg) {
77
if (!seenWarnings.has(msg)) {
78
seenWarnings.add(msg);
79
warn(msg);
80
}
81
}
82
83
let abortExecution = false;
84
85
export function errorOccured() {
86
return abortExecution;
87
}
88
89
export function error(msg, lineNo) {
90
abortExecution = true;
91
process.exitCode = 1;
92
printErr(`error: ${errorPrefix(lineNo)}${msg}`);
93
}
94
95
function range(size) {
96
return Array.from(Array(size).keys());
97
}
98
99
export function mergeInto(obj, other, options = null) {
100
if (options) {
101
// check for unintended symbol redefinition
102
if (options.noOverride) {
103
for (const key of Object.keys(other)) {
104
if (obj.hasOwnProperty(key)) {
105
error(
106
`Symbol re-definition in JavaScript library: ${key}. Do not use noOverride if this is intended`,
107
);
108
return;
109
}
110
}
111
}
112
113
// check if sig is missing for added functions
114
if (options.checkSig) {
115
for (const [key, value] of Object.entries(other)) {
116
if (typeof value === 'function' && !other.hasOwnProperty(key + '__sig')) {
117
error(`__sig is missing for function: ${key}. Do not use checkSig if this is intended`);
118
return;
119
}
120
}
121
}
122
}
123
124
if (!options || !options.allowMissing) {
125
for (const ident of Object.keys(other)) {
126
if (isDecorator(ident)) {
127
const index = ident.lastIndexOf('__');
128
const basename = ident.slice(0, index);
129
if (!(basename in obj) && !(basename in other)) {
130
error(`Missing library element '${basename}' for library config '${ident}'`);
131
}
132
}
133
}
134
}
135
136
for (const key of Object.keys(other)) {
137
if (isDecorator(key)) {
138
if (key.endsWith('__sig')) {
139
if (obj.hasOwnProperty(key)) {
140
const oldsig = obj[key];
141
const newsig = other[key];
142
if (oldsig == newsig) {
143
warn(`signature redefinition for: ${key}`);
144
} else {
145
error(`signature redefinition for: ${key}. (old=${oldsig} vs new=${newsig})`);
146
}
147
}
148
}
149
150
const index = key.lastIndexOf('__');
151
const decoratorName = key.slice(index);
152
const type = typeof other[key];
153
154
if (decoratorName == '__async') {
155
const decorated = key.slice(0, index);
156
if (isJsOnlySymbol(decorated)) {
157
error(`__async decorator applied to JS symbol: ${decorated}`);
158
}
159
}
160
161
// Specific type checking for `__deps` which is expected to be an array
162
// (not just any old `object`)
163
if (decoratorName === '__deps') {
164
const deps = other[key];
165
if (!Array.isArray(deps)) {
166
error(
167
`JS library directive ${key}=${deps} is of type '${type}', but it should be an array`,
168
);
169
}
170
for (let dep of deps) {
171
if (dep && typeof dep !== 'string' && typeof dep !== 'function') {
172
error(
173
`__deps entries must be of type 'string' or 'function' not '${typeof dep}': ${key}`,
174
);
175
}
176
}
177
} else {
178
// General type checking for all other decorators
179
const decoratorTypes = {
180
__sig: 'string',
181
__proxy: 'string',
182
__asm: 'boolean',
183
__postset: ['string', 'function'],
184
__docs: 'string',
185
__nothrow: 'boolean',
186
__noleakcheck: 'boolean',
187
__internal: 'boolean',
188
__user: 'boolean',
189
__async: ['string', 'boolean'],
190
__i53abi: 'boolean',
191
};
192
const expected = decoratorTypes[decoratorName];
193
if (type !== expected && !expected.includes(type)) {
194
error(`Decorator (${key}) has wrong type. Expected '${expected}' not '${type}'`);
195
}
196
}
197
}
198
}
199
200
return Object.assign(obj, other);
201
}
202
203
// Symbols that start with '$' are not exported to the wasm module.
204
// They are intended to be called exclusively by JS code.
205
export function isJsOnlySymbol(symbol) {
206
return symbol[0] == '$';
207
}
208
209
export const decoratorSuffixes = [
210
'__sig',
211
'__proxy',
212
'__asm',
213
'__deps',
214
'__postset',
215
'__docs',
216
'__nothrow',
217
'__noleakcheck',
218
'__internal',
219
'__user',
220
'__async',
221
'__i53abi',
222
];
223
224
export function isDecorator(ident) {
225
return decoratorSuffixes.some((suffix) => ident.endsWith(suffix));
226
}
227
228
export function readFile(filename) {
229
return fs.readFileSync(filename, 'utf8');
230
}
231
232
// Use import.meta.dirname here once we drop support for node v18.
233
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
234
235
export const srcDir = __dirname;
236
237
// Returns an absolute path for a file, resolving it relative to this script
238
// (i.e. relative to the src/ directory).
239
export function localFile(filename) {
240
assert(!path.isAbsolute(filename));
241
return path.join(srcDir, filename);
242
}
243
244
// Helper function for JS library files that can be used to read files
245
// relative to the src/ directory.
246
function read(filename) {
247
if (!path.isAbsolute(filename)) {
248
filename = localFile(filename);
249
}
250
return readFile(filename);
251
}
252
253
export function printErr(...args) {
254
console.error(...args);
255
}
256
257
export function debugLog(...args) {
258
if (VERBOSE) printErr(...args);
259
}
260
261
class Profiler {
262
ids = [];
263
lastTime = 0;
264
265
constructor() {
266
this.start('overall')
267
this.startTime = performance.now();
268
}
269
270
log(msg) {
271
const depth = this.ids.length;
272
const indent = ' '.repeat(depth)
273
printErr('[prof] ' + indent + msg);
274
}
275
276
start(id) {
277
this.log(`-> ${id}`)
278
const now = performance.now();
279
this.ids.push([id, now]);
280
}
281
282
stop(id) {
283
const [poppedId, startTime] = this.ids.pop();
284
assert(id === poppedId);
285
const now = performance.now();
286
const duration = now - startTime;
287
this.log(`<- ${id} [${duration.toFixed(1)} ms]`)
288
}
289
290
terminate() {
291
while (this.ids.length) {
292
const lastID = this.ids[this.ids.length - 1][0];
293
this.stop(lastID);
294
}
295
// const overall = performance.now() - this.startTime
296
// printErr(`overall total: ${overall.toFixed(1)} ms`);
297
}
298
}
299
300
class NullProfiler {
301
start(_id) {}
302
stop(_id) {}
303
terminate() {}
304
}
305
306
// Enable JS compiler profiling if EMPROFILE is "2". This mode reports profile
307
// data to stderr.
308
const EMPROFILE = process.env.EMPROFILE == '2';
309
310
export const timer = EMPROFILE ? new Profiler() : new NullProfiler();
311
312
if (EMPROFILE) {
313
process.on('exit', () => timer.terminate());
314
}
315
316
/**
317
* Context in which JS library code is evaluated. This is distinct from the
318
* global scope of the compiler itself which avoids exposing all of the compiler
319
* internals to user JS library code.
320
*/
321
export const compileTimeContext = vm.createContext({
322
process,
323
console,
324
});
325
326
/**
327
* A symbols to the macro context.
328
* This will makes the symbols available to JS library code at build time.
329
*/
330
export function addToCompileTimeContext(object) {
331
Object.assign(compileTimeContext, object);
332
}
333
334
export function applySettings(obj) {
335
// Make settings available both in the current / global context
336
// and also in the macro execution contexted.
337
Object.assign(globalThis, obj);
338
addToCompileTimeContext(obj);
339
}
340
341
export function loadSettingsFile(f) {
342
timer.start('loadSettingsFile')
343
const settings = {};
344
vm.runInNewContext(readFile(f), settings, {filename: f});
345
applySettings(settings);
346
timer.stop('loadSettingsFile')
347
return settings;
348
}
349
350
export function loadDefaultSettings() {
351
const rtn = loadSettingsFile(localFile('settings.js'));
352
Object.assign(rtn, loadSettingsFile(localFile('settings_internal.js')));
353
return rtn;
354
}
355
356
export function runInMacroContext(code, options) {
357
compileTimeContext['__filename'] = options.filename;
358
compileTimeContext['__dirname'] = path.dirname(options.filename);
359
return vm.runInContext(code, compileTimeContext, options);
360
}
361
362
addToCompileTimeContext({
363
assert,
364
decoratorSuffixes,
365
error,
366
isDecorator,
367
isJsOnlySymbol,
368
mergeInto,
369
read,
370
warn,
371
warnOnce,
372
printErr,
373
range,
374
});
375
376