Skip to content

Commit 9f96cea

Browse files
committed
Process --pre-js and --post-js files in jsifier.js
This teats pre and post JS files more like normal runtime and library files which simplifies the code and avoids the special case processing in emcc.py. This also means that pre and post JS also now go through macro preprocessor which should be useful in some cases.
1 parent 73ab1c5 commit 9f96cea

11 files changed

+73
-40
lines changed

ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ See docs/process.md for more on how version tagging works.
2020

2121
3.1.31 (in development)
2222
-----------------------
23+
- --pre-js and --post-js files are now fed through the JS preprocesor, just
24+
like JS library files and the core runtime JS files. This means they can
25+
now contain #if/#else/#endif blocks and {{{ }}} macro blocks. (#18525)
2326
- The `STACK_SIZE`, `STACK_ALIGN` and `POINTER_SIZE` JavaScript globals were
2427
removed by default. In debug builds a clear error is shown if you try to use
2528
these. (#18503)

emcc.py

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,11 @@ def get_all_js_syms():
524524

525525
# We define a cache hit as when the settings and `--js-library` contents are
526526
# identical.
527-
input_files = [json.dumps(settings.external_dict(), sort_keys=True, indent=2)]
527+
# Ignore certain settings that can are no relevant to library deps. Here we
528+
# skip PRE_JS_FILES/POST_JS_FILES which don't effect the library symbol list
529+
# and can contain full paths to temporary files.
530+
skip_settings = {'PRE_JS_FILES', 'POST_JS_FILES'}
531+
input_files = [json.dumps(settings.external_dict(skip_keys=skip_settings), sort_keys=True, indent=2)]
528532
for jslib in sorted(glob.glob(utils.path_from_root('src') + '/library*.js')):
529533
input_files.append(read_file(jslib))
530534
for jslib in settings.JS_LIBRARIES:
@@ -1119,7 +1123,7 @@ def package_files(options, target):
11191123
if options.preload_files:
11201124
# Preloading files uses --pre-js code that runs before the module is loaded.
11211125
file_code = shared.check_call(cmd, stdout=PIPE).stdout
1122-
options.pre_js = js_manipulation.add_files_pre_js(options.pre_js, file_code)
1126+
js_manipulation.add_files_pre_js(options.pre_js, file_code)
11231127
else:
11241128
# Otherwise, we are embedding files, which does not require --pre-js code,
11251129
# and instead relies on a static constrcutor to populate the filesystem.
@@ -1293,6 +1297,9 @@ def run(args):
12931297
if len(options.preload_files) or len(options.embed_files):
12941298
linker_arguments += package_files(options, target)
12951299

1300+
settings.PRE_JS_FILES = [os.path.abspath(f) for f in options.pre_js]
1301+
settings.POST_JS_FILES = [os.path.abspath(f) for f in options.post_js]
1302+
12961303
if options.oformat == OFormat.OBJECT:
12971304
logger.debug(f'link_to_object: {linker_arguments} -> {target}')
12981305
building.link_to_object(linker_arguments, target)
@@ -1750,8 +1757,6 @@ def phase_linker_setup(options, state, newargs):
17501757
exit_with_error('PTHREADS_PROFILING only works with ASSERTIONS enabled')
17511758
options.post_js.append(utils.path_from_root('src/threadprofiler.js'))
17521759

1753-
options.pre_js = read_js_files(options.pre_js)
1754-
options.post_js = read_js_files(options.post_js)
17551760
options.extern_pre_js = read_js_files(options.extern_pre_js)
17561761
options.extern_post_js = read_js_files(options.extern_post_js)
17571762

@@ -3021,7 +3026,8 @@ def phase_post_link(options, state, in_wasm, wasm_target, target):
30213026

30223027
phase_emscript(options, in_wasm, wasm_target, memfile)
30233028

3024-
phase_source_transforms(options)
3029+
if options.js_transform:
3030+
phase_source_transforms(options)
30253031

30263032
if memfile and not settings.MINIMAL_RUNTIME:
30273033
# MINIMAL_RUNTIME doesn't use `var memoryInitializer` but instead expects Module['mem'] to
@@ -3055,28 +3061,14 @@ def phase_emscript(options, in_wasm, wasm_target, memfile):
30553061

30563062
@ToolchainProfiler.profile_block('source transforms')
30573063
def phase_source_transforms(options):
3058-
global final_js
3059-
3060-
# Apply pre and postjs files
3061-
if final_js and (options.pre_js or options.post_js):
3062-
logger.debug('applying pre/postjses')
3063-
src = read_file(final_js)
3064-
final_js += '.pp.js'
3065-
with open(final_js, 'w', encoding='utf-8') as f:
3066-
# pre-js code goes right after the Module integration code (so it
3067-
# can use Module), we have a marker for it
3068-
f.write(do_replace(src, '// {{PRE_JSES}}', options.pre_js))
3069-
f.write(options.post_js)
3070-
save_intermediate('pre-post')
3071-
30723064
# Apply a source code transformation, if requested
3073-
if options.js_transform:
3074-
safe_copy(final_js, final_js + '.tr.js')
3075-
final_js += '.tr.js'
3076-
posix = not shared.WINDOWS
3077-
logger.debug('applying transform: %s', options.js_transform)
3078-
shared.check_call(building.remove_quotes(shlex.split(options.js_transform, posix=posix) + [os.path.abspath(final_js)]))
3079-
save_intermediate('transformed')
3065+
global final_js
3066+
safe_copy(final_js, final_js + '.tr.js')
3067+
final_js += '.tr.js'
3068+
posix = not shared.WINDOWS
3069+
logger.debug('applying transform: %s', options.js_transform)
3070+
shared.check_call(building.remove_quotes(shlex.split(options.js_transform, posix=posix) + [os.path.abspath(final_js)]))
3071+
save_intermediate('transformed')
30803072

30813073

30823074
@ToolchainProfiler.profile_block('memory initializer')

src/jsifier.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,10 @@ function ${name}(${args}) {
530530
const postFile = MINIMAL_RUNTIME ? 'postamble_minimal.js' : 'postamble.js';
531531
includeFile(postFile);
532532

533+
for (const fileName of POST_JS_FILES) {
534+
includeFile(fileName);
535+
}
536+
533537
print('//FORWARDED_DATA:' + JSON.stringify({
534538
librarySymbols: librarySymbols,
535539
warnings: warnings,

src/parseTools.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,3 +1104,12 @@ function getUnsharedTextDecoderView(heap, start, end) {
11041104
// or can use .subarray() otherwise.
11051105
return `${heap}.buffer instanceof SharedArrayBuffer ? ${shared} : ${unshared}`;
11061106
}
1107+
1108+
function preJS() {
1109+
let result = '';
1110+
for (const fileName of PRE_JS_FILES) {
1111+
result += preprocess(fileName);
1112+
}
1113+
return result;
1114+
}
1115+

src/settings_internal.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,7 @@ var WEAK_IMPORTS = [];
243243
var STACK_FIRST = false;
244244

245245
var HAVE_EM_ASM = true;
246+
247+
var PRE_JS_FILES = [];
248+
249+
var POST_JS_FILES = [];

src/shell.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Module['ready'] = new Promise(function(resolve, reject) {
7272

7373
// --pre-jses are emitted after the Module integration code, so that they can
7474
// refer to Module (if they choose; they can also define Module)
75-
// {{PRE_JSES}}
75+
{{{ preJS() }}}
7676

7777
// Sometimes an existing Module object exists with properties
7878
// meant to overwrite the default module functionality. Here

src/shell_minimal.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,7 @@ function ready() {
136136

137137
// --pre-jses are emitted after the Module integration code, so that they can
138138
// refer to Module (if they choose; they can also define Module)
139-
140-
// {{PRE_JSES}}
139+
{{{ preJS() }}}
141140

142141
#if USE_PTHREADS
143142

test/return64bit/testbindstart.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
2-
(function() { // Start of self-calling lambda used to avoid polluting global namespace.
3-
1+
(function() { // Start of IIFE used to avoid polluting global namespace.

test/test_other.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9048,6 +9048,21 @@ def test_js_preprocess(self):
90489048
self.assertContained('JSLIB: EXIT_RUNTIME', err)
90499049
self.assertNotContained('JSLIB: MAIN_MODULE', err)
90509050

9051+
def test_js_preprocess_pre_post(self):
9052+
create_file('pre.js', '''
9053+
#if ASSERTIONS
9054+
console.log('assertions enabled')
9055+
#else
9056+
console.log('assertions disabled')
9057+
#endif
9058+
''')
9059+
create_file('post.js', '''
9060+
console.log({{{ POINTER_SIZE }}});
9061+
''')
9062+
self.emcc_args += ['--pre-js', 'pre.js', '--post-js', 'post.js']
9063+
self.do_runf(test_file('hello_world.c'), 'assertions enabled\n4')
9064+
self.do_runf(test_file('hello_world.c'), 'assertions disabled\n4', emcc_args=['-sASSERTIONS=0'])
9065+
90519066
def test_html_preprocess(self):
90529067
src_file = test_file('module/test_stdin.c')
90539068
output_file = 'test_stdin.html'

tools/js_manipulation.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import re
77

88
from .settings import settings
9-
from . import utils
9+
from . import utils, shared
1010

1111
emscripten_license = '''\
1212
/**
@@ -28,25 +28,34 @@
2828
emscripten_license_regex = r'\/\*\*?(\s*\*?\s*@license)?(\s*\*?\s*Copyright \d+ The Emscripten Authors\s*\*?\s*SPDX-License-Identifier: MIT)+\s*\*\/\s*'
2929

3030

31-
def add_files_pre_js(user_pre_js, files_pre_js):
31+
def add_files_pre_js(pre_js_list, files_pre_js):
3232
# the normal thing is to just combine the pre-js content
33+
filename = shared.get_temp_files().get('.js').name
34+
utils.write_file(filename, files_pre_js)
35+
pre_js_list.insert(0, filename)
3336
if not settings.ASSERTIONS:
34-
return files_pre_js + user_pre_js
37+
return
3538

3639
# if a user pre-js tramples the file code's changes to Module.preRun
3740
# that could be confusing. show a clear error at runtime if assertions are
3841
# enabled
39-
return files_pre_js + '''
42+
pre = shared.get_temp_files().get('.js').name
43+
post = shared.get_temp_files().get('.js').name
44+
utils.write_file(pre, '''
4045
// All the pre-js content up to here must remain later on, we need to run
4146
// it.
4247
if (Module['ENVIRONMENT_IS_PTHREAD']) Module['preRun'] = [];
4348
var necessaryPreJSTasks = Module['preRun'].slice();
44-
''' + user_pre_js + '''
49+
''')
50+
utils.write_file(post, '''
4551
if (!Module['preRun']) throw 'Module.preRun should exist because file support used it; did a pre-js delete it?';
4652
necessaryPreJSTasks.forEach(function(task) {
4753
if (Module['preRun'].indexOf(task) < 0) throw 'All preRun tasks that exist before user pre-js code should remain after; did you replace Module or modify Module.preRun?';
4854
});
49-
'''
55+
''')
56+
57+
pre_js_list.insert(1, pre)
58+
pre_js_list.append(post)
5059

5160

5261
def handle_license(js_target):

tools/settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,8 @@ def infer_types(self):
156156
def dict(self):
157157
return self.attrs
158158

159-
def external_dict(self):
160-
external_settings = {k: v for k, v in self.dict().items() if k not in INTERNAL_SETTINGS}
159+
def external_dict(self, skip_keys={}):
160+
external_settings = {k: v for k, v in self.dict().items() if k not in INTERNAL_SETTINGS and k not in skip_keys}
161161
# Only the names of the legacy settings are used by the JS compiler
162162
# so we can reduce the size of serialized json by simplifying this
163163
# otherwise complex value.

0 commit comments

Comments
 (0)