Skip to content

Commit ea85994

Browse files
authored
INCOMING_MODULE_JS_API (#9072)
This new setting lets the user define which Module arguments will be provided at runtime. By default we assume, as always, that the user may provide any such API. With this new setting, the compiler can be told at compile time that only some of those values may be arriving. That allows it to not emit code for all the options that are not. This starts to optimize using that option, but only does a little work so far. Specifically, Module['arguments'], Module['thisProgram'], Module['quit'] are all optimized, that is, they are no longer placed on Module, and they are not read from Module unless the user expects them to be provided at runtime. (Again, that is the default, so no existing code should break.) On hello world this saves 2.5%, but again, this option lets us optimize a lot more later. In principle, I think this option is key to letting us remove the bulk of unnecessary code from our minimal JS, in fact.
1 parent ddc190b commit ea85994

12 files changed

+124
-30
lines changed

emcc.py

+3
Original file line numberDiff line numberDiff line change
@@ -1154,6 +1154,9 @@ def check(input_file):
11541154

11551155
if shared.Settings.MODULARIZE:
11561156
assert not options.proxy_to_worker, '-s MODULARIZE=1 and -s MODULARIZE_INSTANCE=1 are not compatible with --proxy-to-worker (if you want to run in a worker with -s MODULARIZE=1, you likely want to do the worker side setup manually)'
1157+
# MODULARIZE's .then() method uses onRuntimeInitialized currently, so make sure
1158+
# it is expected to be used.
1159+
shared.Settings.INCOMING_MODULE_JS_API += ['onRuntimeInitialized']
11571160

11581161
if shared.Settings.EMULATE_FUNCTION_POINTER_CASTS:
11591162
shared.Settings.ALIASING_FUNCTION_POINTERS = 0

src/compiler.js

+1
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ if (settings_file) {
168168
EXPORTED_FUNCTIONS = set(EXPORTED_FUNCTIONS);
169169
EXCEPTION_CATCHING_WHITELIST = set(EXCEPTION_CATCHING_WHITELIST);
170170
IMPLEMENTED_FUNCTIONS = set(IMPLEMENTED_FUNCTIONS);
171+
INCOMING_MODULE_JS_API = set(INCOMING_MODULE_JS_API);
171172

172173
DEAD_FUNCTIONS.forEach(function(dead) {
173174
DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.push(dead.substr(1));

src/emrun_prejs.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
if (typeof window === "object") {
88
Module['arguments'] = window.location.search.substr(1).trim().split('&');
99
// If no args were passed arguments = [''], in which case kill the single empty string.
10-
if (!Module['arguments'][0])
10+
if (!Module['arguments'][0]) {
1111
Module['arguments'] = [];
12-
}
12+
}
13+
}

src/library.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,7 @@ LibraryManager.library = {
898898
ENV['LANG'] = 'C.UTF-8';
899899
// Browser language detection #8751
900900
ENV['LANG'] = ((typeof navigator === 'object' && navigator.languages && navigator.languages[0]) || 'C').replace('-', '_') + '.UTF-8';
901-
ENV['_'] = Module['thisProgram'];
901+
ENV['_'] = thisProgram;
902902
// Allocate memory.
903903
#if !MINIMAL_RUNTIME // TODO: environment support in MINIMAL_RUNTIME
904904
poolPtr = getMemory(TOTAL_ENV_SIZE);
@@ -1981,7 +1981,7 @@ LibraryManager.library = {
19811981
dladdr__sig: 'iii',
19821982
dladdr: function(addr, info) {
19831983
// report all function pointers as coming from this program itself XXX not really correct in any way
1984-
var fname = stringToNewUTF8(Module['thisProgram'] || './this.program'); // XXX leak
1984+
var fname = stringToNewUTF8(thisProgram || './this.program'); // XXX leak
19851985
{{{ makeSetValue('info', 0, 'fname', 'i32') }}};
19861986
{{{ makeSetValue('info', Runtime.QUANTUM_SIZE, '0', 'i32') }}};
19871987
{{{ makeSetValue('info', Runtime.QUANTUM_SIZE*2, '0', 'i32') }}};

src/parseTools.js

+15
Original file line numberDiff line numberDiff line change
@@ -1599,3 +1599,18 @@ function modifyFunction(text, func) {
15991599
assert(bodyEnd > 0);
16001600
return func(name, args, rest.substring(bodyStart + 1, bodyEnd));
16011601
}
1602+
1603+
function expectToReceiveOnModule(name) {
1604+
return name in INCOMING_MODULE_JS_API;
1605+
}
1606+
1607+
// Make code to receive a value on the incoming Module object.
1608+
function makeModuleReceive(localName, moduleName) {
1609+
if (!moduleName) moduleName = localName;
1610+
if (!expectToReceiveOnModule(moduleName)) {
1611+
return '';
1612+
}
1613+
// Usually the local we use is the same as the Module property name,
1614+
// but sometimes they must differ.
1615+
return "if (Module['" + moduleName + "']) " + localName + " = Module['" + moduleName + "'];";
1616+
}

src/postamble.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ Module['then'] = function(func) {
130130
} else {
131131
// we are not ready to call then() yet. we must call it
132132
// at the same time we would call onRuntimeInitialized.
133+
#if ASSERTIONS && !expectToReceiveOnModule('onRuntimeInitialized')
134+
abort('.then() requires adding onRuntimeInitialized to INCOMING_MODULE_JS_API');
135+
#endif
133136
var old = Module['onRuntimeInitialized'];
134137
Module['onRuntimeInitialized'] = function() {
135138
if (old) old();
@@ -174,7 +177,7 @@ Module['callMain'] = function callMain(args) {
174177

175178
var argc = args.length+1;
176179
var argv = stackAlloc((argc + 1) * {{{ Runtime.POINTER_SIZE }}});
177-
HEAP32[argv >> 2] = allocateUTF8OnStack(Module['thisProgram']);
180+
HEAP32[argv >> 2] = allocateUTF8OnStack(thisProgram);
178181
for (var i = 1; i < argc; i++) {
179182
HEAP32[(argv >> 2) + i] = allocateUTF8OnStack(args[i - 1]);
180183
}
@@ -235,7 +238,7 @@ Module['callMain'] = function callMain(args) {
235238
toLog = [e, e.stack];
236239
}
237240
err('exception thrown: ' + toLog);
238-
Module['quit'](1, e);
241+
quit_(1, e);
239242
}
240243
} finally {
241244
calledMain = true;
@@ -247,7 +250,7 @@ Module['callMain'] = function callMain(args) {
247250

248251
/** @type {function(Array=)} */
249252
function run(args) {
250-
args = args || Module['arguments'];
253+
args = args || arguments_;
251254

252255
if (runDependencies > 0) {
253256
#if RUNTIME_LOGGING
@@ -275,7 +278,9 @@ function run(args) {
275278

276279
preMain();
277280

281+
#if expectToReceiveOnModule('onRuntimeInitialized')
278282
if (Module['onRuntimeInitialized']) Module['onRuntimeInitialized']();
283+
#endif
279284

280285
#if HAS_MAIN
281286
if (Module['_main'] && shouldRunNow) Module['callMain'](args);
@@ -397,7 +402,7 @@ function exit(status, implicit) {
397402
if (Module['onExit']) Module['onExit'](status);
398403
}
399404

400-
Module['quit'](status, new ExitStatus(status));
405+
quit_(status, new ExitStatus(status));
401406
}
402407

403408
var abortDecorators = [];

src/preamble.js

+22-4
Original file line numberDiff line numberDiff line change
@@ -333,12 +333,16 @@ var TOTAL_STACK = {{{ TOTAL_STACK }}};
333333
#if ASSERTIONS
334334
if (Module['TOTAL_STACK']) assert(TOTAL_STACK === Module['TOTAL_STACK'], 'the stack size can no longer be determined at runtime')
335335
#endif
336+
#if MAIN_MODULE && !WASM
337+
// JS side modules use this value to decide their stack size.
338+
Module['TOTAL_STACK'] = TOTAL_STACK;
339+
#endif
336340

337341
var INITIAL_TOTAL_MEMORY = Module['TOTAL_MEMORY'] || {{{ TOTAL_MEMORY }}};
338-
if (INITIAL_TOTAL_MEMORY < TOTAL_STACK) err('TOTAL_MEMORY should be larger than TOTAL_STACK, was ' + INITIAL_TOTAL_MEMORY + '! (TOTAL_STACK=' + TOTAL_STACK + ')');
339342

340-
// Initialize the runtime's memory
341343
#if ASSERTIONS
344+
assert(INITIAL_TOTAL_MEMORY >= TOTAL_STACK, 'TOTAL_MEMORY should be larger than TOTAL_STACK, was ' + INITIAL_TOTAL_MEMORY + '! (TOTAL_STACK=' + TOTAL_STACK + ')');
345+
342346
// check for full engine support (use string 'subarray' to avoid closure compiler confusion)
343347
assert(typeof Int32Array !== 'undefined' && typeof Float64Array !== 'undefined' && Int32Array.prototype.subarray !== undefined && Int32Array.prototype.set !== undefined,
344348
'JS engine does not provide full typed array support');
@@ -490,13 +494,16 @@ function preRun() {
490494
#if USE_PTHREADS
491495
if (ENVIRONMENT_IS_PTHREAD) return; // PThreads reuse the runtime from the main thread.
492496
#endif
493-
// compatibility - merge in anything from Module['preRun'] at this time
497+
498+
#if expectToReceiveOnModule('preRun')
494499
if (Module['preRun']) {
495500
if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']];
496501
while (Module['preRun'].length) {
497502
addOnPreRun(Module['preRun'].shift());
498503
}
499504
}
505+
#endif
506+
500507
callRuntimeCallbacks(__ATPRERUN__);
501508
}
502509

@@ -544,13 +551,16 @@ function postRun() {
544551
#if USE_PTHREADS
545552
if (ENVIRONMENT_IS_PTHREAD) return; // PThreads reuse the runtime from the main thread.
546553
#endif
547-
// compatibility - merge in anything from Module['postRun'] at this time
554+
555+
#if expectToReceiveOnModule('postRun')
548556
if (Module['postRun']) {
549557
if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']];
550558
while (Module['postRun'].length) {
551559
addOnPostRun(Module['postRun'].shift());
552560
}
553561
}
562+
#endif
563+
554564
callRuntimeCallbacks(__ATPOSTRUN__);
555565
}
556566

@@ -613,9 +623,13 @@ function addRunDependency(id) {
613623
assert(!ENVIRONMENT_IS_PTHREAD, "addRunDependency cannot be used in a pthread worker");
614624
#endif
615625
runDependencies++;
626+
627+
#if expectToReceiveOnModule('monitorRunDependencies')
616628
if (Module['monitorRunDependencies']) {
617629
Module['monitorRunDependencies'](runDependencies);
618630
}
631+
#endif
632+
619633
#if ASSERTIONS
620634
if (id) {
621635
assert(!runDependencyTracking[id]);
@@ -649,9 +663,13 @@ function addRunDependency(id) {
649663

650664
function removeRunDependency(id) {
651665
runDependencies--;
666+
667+
#if expectToReceiveOnModule('monitorRunDependencies')
652668
if (Module['monitorRunDependencies']) {
653669
Module['monitorRunDependencies'](runDependencies);
654670
}
671+
#endif
672+
655673
#if ASSERTIONS
656674
if (id) {
657675
assert(runDependencyTracking[id]);

src/settings.js

+37
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,43 @@ var EXPORTED_RUNTIME_METHODS = [];
578578
// this list lets you add to the default list without modifying it.
579579
var EXTRA_EXPORTED_RUNTIME_METHODS = [];
580580

581+
// A list of incoming values on the Module object in JS that we care about. If
582+
// a value is not in this list, then we don't emit code to check if you provide
583+
// it on the Module object. For example, if
584+
// you have this:
585+
//
586+
// var Module = {
587+
// print: function(x) { console.log('print: ' + x) },
588+
// preRun: [function() { console.log('pre run') }]
589+
// };
590+
//
591+
// Then MODULE_JS_API must contain 'print' and 'preRun'; if it does not then
592+
// we may not emit code to read and use that value. In other words, this
593+
// option lets you set, statically at compile time, the list of which Module
594+
// JS values you will be providing at runtime, so the compiler can better
595+
// optimize.
596+
//
597+
// Setting this list to [], or at least a short and concise set of names you
598+
// actually use, can be very useful for reducing code size. By default the
599+
// list contains all the possible APIs.
600+
//
601+
// FIXME: should this just be 0 if we want everything?
602+
var INCOMING_MODULE_JS_API = [
603+
'ENVIRONMENT', 'GL_MAX_TEXTURE_IMAGE_UNITS', 'SDL_canPlayWithWebAudio',
604+
'SDL_numSimultaneouslyQueuedBuffers', 'TOTAL_MEMORY', 'wasmMemory', 'arguments',
605+
'buffer', 'canvas', 'doNotCaptureKeyboard', 'dynamicLibraries',
606+
'elementPointerLock', 'extraStackTrace', 'forcedAspectRatio',
607+
'instantiateWasm', 'keyboardListeningElementfreePreloadedMediaOnUse',
608+
'locateFile', 'logReadFiles', 'mainScriptUrlOrBlob', 'mem',
609+
'monitorRunDependencies', 'noExitRuntime', 'noInitialRun', 'onAbort',
610+
'onCustomMessage', 'onExit', 'onFree', 'onFullScreen', 'onMalloc',
611+
'onRealloc', 'onRuntimeInitialized', 'postMainLoop', 'postRun', 'preInit',
612+
'preMainLoop', 'preRun',
613+
'preinitializedWebGLContextmemoryInitializerRequest', 'preloadPlugins',
614+
'print', 'printErr', 'quit', 'setStatus', 'statusMessage', 'stderr',
615+
'stdin', 'stdout', 'thisProgram', 'wasm', 'wasmBinary', 'websocket'
616+
];
617+
581618
// If set to nonzero, the provided virtual filesystem if treated
582619
// case-insensitive, like Windows and macOS do. If set to 0, the VFS is
583620
// case-sensitive, like on Linux.

src/shell.js

+19-13
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,11 @@ for (key in Module) {
4343
}
4444
}
4545

46-
Module['arguments'] = [];
47-
Module['thisProgram'] = './this.program';
48-
Module['quit'] = function(status, toThrow) {
46+
var arguments_ = [];
47+
var thisProgram = './this.program';
48+
var quit_ = function(status, toThrow) {
4949
throw toThrow;
5050
};
51-
Module['preRun'] = [];
52-
Module['postRun'] = [];
5351

5452
// Determine the runtime environment we are in. You can customize this by
5553
// setting the ENVIRONMENT setting at compile time (see settings.js).
@@ -152,10 +150,10 @@ if (ENVIRONMENT_IS_NODE) {
152150
};
153151

154152
if (process['argv'].length > 1) {
155-
Module['thisProgram'] = process['argv'][1].replace(/\\/g, '/');
153+
thisProgram = process['argv'][1].replace(/\\/g, '/');
156154
}
157155

158-
Module['arguments'] = process['argv'].slice(2);
156+
arguments_ = process['argv'].slice(2);
159157

160158
#if MODULARIZE
161159
// MODULARIZE will export the module in the proper place outside, we don't need to export here
@@ -177,7 +175,7 @@ if (ENVIRONMENT_IS_NODE) {
177175
// deprecated, and in the future it will exit with error status.
178176
process['on']('unhandledRejection', abort);
179177

180-
Module['quit'] = function(status) {
178+
quit_ = function(status) {
181179
process['exit'](status);
182180
};
183181

@@ -222,15 +220,15 @@ if (ENVIRONMENT_IS_SHELL) {
222220
};
223221

224222
if (typeof scriptArgs != 'undefined') {
225-
Module['arguments'] = scriptArgs;
223+
arguments_ = scriptArgs;
226224
} else if (typeof arguments != 'undefined') {
227-
Module['arguments'] = arguments;
225+
arguments_ = arguments;
228226
}
229227

230228
if (typeof quit === 'function') {
231-
Module['quit'] = function(status) {
229+
quit_ = function(status) {
232230
quit(status);
233-
}
231+
};
234232
}
235233
} else
236234
#endif // ENVIRONMENT_MAY_BE_SHELL
@@ -353,7 +351,15 @@ for (key in moduleOverrides) {
353351
}
354352
// Free the object hierarchy contained in the overrides, this lets the GC
355353
// reclaim data used e.g. in memoryInitializerRequest, which is a large typed array.
356-
moduleOverrides = undefined;
354+
moduleOverrides = null;
355+
356+
// Emit code to handle expected values on the Module object. This applies Module.x
357+
// to the proper local x. This has two benefits: first, we only emit it if it is
358+
// expected to arrive, and second, by using a local everywhere else that can be
359+
// minified.
360+
{{{ makeModuleReceive('arguments_', 'arguments') }}}
361+
{{{ makeModuleReceive('thisProgram') }}}
362+
{{{ makeModuleReceive('quit_', 'quit') }}}
357363

358364
// perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message
359365
#if ASSERTIONS

src/shell_sharedlib.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212

1313
var gb = 0;
1414
// Each module has its own stack
15-
var STACKTOP = getMemory(TOTAL_STACK);
15+
var STACKTOP = getMemory(parentModule['TOTAL_STACK']);
1616
assert(STACKTOP % 8 == 0);
17-
var STACK_MAX = STACKTOP + TOTAL_STACK;
17+
var STACK_MAX = STACKTOP + parentModule['TOTAL_STACK'];
1818
Module.cleanups.push(function() {
1919
parentModule['_free'](STACKTOP); // XXX ensure exported, and that it was actually malloc'ed and not static memory FIXME
2020
parentModule['_free'](gb);

tests/test_core.py

-3
Original file line numberDiff line numberDiff line change
@@ -1868,9 +1868,6 @@ def test():
18681868

18691869
if '-O2' in self.emcc_args and not self.is_wasm():
18701870
# Make sure ALLOW_MEMORY_GROWTH generates different code (should be less optimized)
1871-
code_start = 'var TOTAL_STACK'
1872-
fail = fail[fail.find(code_start):]
1873-
win = win[win.find(code_start):]
18741871
assert len(fail) < len(win), 'failing code - without memory growth on - is more optimized, and smaller' + str([len(fail), len(win)])
18751872

18761873
test()

tests/test_other.py

+11
Original file line numberDiff line numberDiff line change
@@ -9494,6 +9494,17 @@ def test_main_reads_params(self):
94949494
# otherwise in such a trivial program).
94959495
self.assertLess(no, 0.95 * yes)
94969496

9497+
@no_fastcomp('not optimized in fastcomp')
9498+
def test_INCOMING_MODULE_JS_API(self):
9499+
def test(args):
9500+
run_process([PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-O3'] + args)
9501+
self.assertContained('hello, world!', run_js('a.out.js'))
9502+
return os.path.getsize('a.out.js')
9503+
normal = test([])
9504+
changed = test(['-s', 'INCOMING_MODULE_JS_API=[]'])
9505+
# TODO: specific sizes once we stabilize
9506+
self.assertLess(changed, normal)
9507+
94979508
def test_llvm_includes(self):
94989509
self.build('#include <stdatomic.h>', self.get_dir(), 'atomics.c')
94999510

0 commit comments

Comments
 (0)