Skip to content

Commit 7e57b9b

Browse files
committed
[Bug 16076][emscripten] Generate Emterpreter whitelist more cleverly
Apply two sets of regular expressions to the symbols in the engine: - Only symbols that match a whitelist expression are compiled with Emterpreter - No symbol that matches a blacklist expression is compiled with Emterpreter The set of regular expressions provided in this patch seems to cover most possible ways that the engine could end up waiting, but it's not guaranteed to be exhausted. Just compiling everything in the core engine to Emtepreter bytecode made the engine very large and very slow, so a more selective approach was required.
1 parent d7d6940 commit 7e57b9b

File tree

7 files changed

+229
-57
lines changed

7 files changed

+229
-57
lines changed

docs/notes/bugfix-16076.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Enable "wait" syntax in HTML5 standalones

engine/engine.gyp

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,38 @@
764764

765765
'actions':
766766
[
767+
{
768+
'action_name': 'genwhitelist',
769+
'message': 'Generating the Emterpreter whitelist',
770+
771+
'inputs':
772+
[
773+
'../util/emscripten-genwhitelist.py',
774+
'<(PRODUCT_DIR)/standalone-community.bc',
775+
'src/em-whitelist.json',
776+
'src/em-blacklist.json',
777+
],
778+
779+
'outputs':
780+
[
781+
'<(PRODUCT_DIR)/standalone-community-whitelist.json',
782+
'<(PRODUCT_DIR)/standalone-community-blacklist.json',
783+
],
784+
785+
'action':
786+
[
787+
'../util/emscripten-genwhitelist.py',
788+
'--input',
789+
'<(PRODUCT_DIR)/standalone-community.bc',
790+
'--output',
791+
'<(PRODUCT_DIR)/standalone-community-whitelist.json',
792+
'<(PRODUCT_DIR)/standalone-community-blacklist.json',
793+
'--include',
794+
'src/em-whitelist.json',
795+
'--exclude',
796+
'src/em-blacklist.json',
797+
],
798+
},
767799
{
768800
'action_name': 'javascriptify',
769801
'message': 'Javascript-ifying the Emscripten engine',
@@ -773,7 +805,7 @@
773805
'../util/emscripten-javascriptify.py',
774806
'<(PRODUCT_DIR)/standalone-community.bc',
775807
'rsrc/emscripten-html-template.html',
776-
'src/em-whitelist.json',
808+
'<(PRODUCT_DIR)/standalone-community-whitelist.json',
777809
'src/em-preamble.js',
778810
'src/em-preamble-overlay.js',
779811
'src/em-util.js',
@@ -802,7 +834,7 @@
802834
'--shell-file',
803835
'rsrc/emscripten-html-template.html',
804836
'--whitelist',
805-
'src/em-whitelist.json',
837+
'<(PRODUCT_DIR)/standalone-community-whitelist.json',
806838
'--pre-js',
807839
'src/em-preamble.js',
808840
'src/em-preamble-overlay.js',

engine/src/em-blacklist.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[
2+
"ER13MCScriptPoint",
3+
"D[0-9]+Ev"
4+
]

engine/src/em-whitelist.json

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,56 @@
11
[
2-
"_main",
3-
"__Z13platform_mainiPPcS0_",
4-
"__Z21X_main_loop_iterationv",
5-
"__ZN10MCScreenDC4waitEdhh",
6-
7-
"_MCEventQueueDispatch",
8-
"__ZL25MCEventQueueDispatchEventP7MCEvent",
9-
10-
"__ZN6MCCard3mupEtb",
11-
"__ZN7MCStack3mupEtb",
12-
"__ZN8MCButton3mupEtb",
13-
14-
"__ZN8MCObject26message_with_valueref_argsEP8__MCNamePv",
15-
"__ZN8MCObject7messageEP8__MCNameP11MCParameterhhh",
16-
"__ZN8MCObject6handleE12Handler_typeP8__MCNameP11MCParameterPS_",
17-
"__ZN8MCObject10handleselfE12Handler_typeP8__MCNameP11MCParameter",
18-
"__ZN8MCObject11exechandlerEP9MCHandlerP11MCParameter",
19-
20-
"__ZN9MCHandler4execER13MCExecContextP11MCParameter",
21-
22-
"__ZN5MCPut9exec_ctxtER13MCExecContext",
23-
24-
"__ZN13MCExecContext18EvaluateExpressionEP12MCExpression11Exec_errorsR11MCExecValue",
25-
26-
"__ZN7MCChunk9eval_ctxtER13MCExecContextR11MCExecValue",
27-
28-
"__Z10MCU_geturlR13MCExecContextP10__MCStringRPv",
29-
30-
"__Z10MCS_geturlP8MCObjectP10__MCString",
31-
32-
"_MCEmscriptenAsyncYield"
2+
"^_main$",
3+
"^__Z6X_initiPP10__MCStringiS1_$",
4+
"^__Z6X_openiPP10__MCStringS1_$",
5+
"^__ZN10MCDispatch7startupEv$",
6+
"^__Z20send_startup_messageb",
7+
"^__Z13platform_mainiPPcS0_$",
8+
"^__Z21X_main_loop_iterationv$",
9+
10+
"^_MCEventQueueDispatch$",
11+
"^__ZL25MCEventQueueDispatchEventP7MCEvent$",
12+
"^_MCEmscriptenAsyncYield$",
13+
14+
"MCWidgetExec",
15+
"MCWidgetOn",
16+
17+
"AddRunloopAction",
18+
"DoRunloopActions",
19+
20+
"(exec|eval)_ctxt",
21+
"4wait",
22+
"26message_with_valueref_args",
23+
"addmessage",
24+
"7message",
25+
"6handle",
26+
"13handlepending",
27+
"10handleself",
28+
"11exechandler",
29+
"[0-9]+close",
30+
"3del",
31+
"help",
32+
"[0-9]+(k|m)(focus|down|up)",
33+
"mdrag",
34+
"paste",
35+
"doubledown",
36+
"layerchanged",
37+
"resizeparent",
38+
"sync_mfocus",
39+
"toolchanged",
40+
"wdoubledown",
41+
"wmdragenter",
42+
"wmdragleave",
43+
"wmfocus_stack",
44+
45+
"MCExecContext[0-9]+(TryTo)?[Ee]val",
46+
"MCEngineEvalValue",
47+
"MCEngine\\w*Wait",
48+
"MCEngineExecDispatch",
49+
"MCEngineExecDo",
50+
"MCEngineExecSend",
51+
52+
"MC[SU]_\\w*url(?!(en|de)code)",
53+
54+
"__ZN9MCHandler4exec",
55+
"MCKeywordsExec"
3356
]

tests/lcs/core/engine/engine.livecodescript

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,6 @@ end TestVersion
287287

288288

289289
on TestCancelPendingMessage
290-
if the platform is "HTML" then
291-
TestSkip "cancel message", "bug 16076"
292-
exit TestCancelPendingMessage
293-
end if
294290

295291
create button
296292

@@ -429,10 +425,6 @@ end TestReleaseStackMessage
429425

430426

431427
on TestMilliseconds
432-
if the platform is "HTML" then
433-
TestSkip "cancel message", "bug 16076"
434-
exit TestMilliseconds
435-
end if
436428

437429
local tTime
438430

@@ -446,10 +438,6 @@ end TestMilliseconds
446438

447439

448440
on TestSeconds
449-
if the platform is "HTML" then
450-
TestSkip "cancel message", "bug 16076"
451-
exit TestSeconds
452-
end if
453441

454442
local tTime
455443

@@ -463,10 +451,6 @@ end TestSeconds
463451

464452

465453
on TestTicks
466-
if the platform is "HTML" then
467-
TestSkip "cancel message", "bug 16076"
468-
exit TestTicks
469-
end if
470454

471455
local tTime
472456

tests/lcs/core/interface/interface.livecodescript

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,6 @@ end TestMouse
228228

229229

230230
on TestMovingControls
231-
if the platform is "HTML" then
232-
TestSkip "moving button", "bug 16076"
233-
exit TestMovingControls
234-
end if
235231

236232
TestAssert "No moving controls", the movingControls is empty
237233
create button
@@ -699,10 +695,6 @@ end TestSortField
699695

700696

701697
on TestMoveControl
702-
if the platform is "HTML" then
703-
TestSkip "stop moving button", "bug 16076"
704-
exit TestMoveControl
705-
end if
706698

707699
create button
708700

util/emscripten-genwhitelist.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env python
2+
3+
# This script extracts symbols from an LLVM bitcode file, and uses
4+
# them to generate a whitelist of symbols that should be compiled with
5+
# Emterpreter.
6+
#
7+
# There are two sets of regular expressions loaded from JSON files:
8+
# "include" expressions are used to find functions that should be
9+
# Emterpreted, and "exclude" expressions are used to prune out
10+
# functions that are too greedily included.
11+
12+
import sys
13+
import os
14+
import subprocess
15+
import re
16+
import json
17+
18+
# Get any relevant info from the environment
19+
# ------------------------------------------
20+
21+
env_verbose = os.getenv('V', '0').strip()
22+
env_nm = os.getenv('NM', 'llvm-nm')
23+
24+
# Separate out separate elements of command line
25+
nm = env_nm.split()
26+
27+
# Verbosity
28+
verbose = (env_verbose.strip() is not '0')
29+
30+
# Process command line options
31+
# ----------------------------
32+
#
33+
# Each option absorbs all subsequent arguments up to the next option.
34+
# Options are identified by the fact they start with "--".
35+
36+
option = None
37+
options = {}
38+
for arg in sys.argv[1:]:
39+
if arg.startswith('--'):
40+
option = arg[2:]
41+
options[option] = []
42+
else:
43+
if option is None:
44+
print('ERROR: unrecognized option \'{}\''.format(arg))
45+
sys.exit(1)
46+
options[option].append(arg)
47+
48+
# Generate include/exclude predicate
49+
# ----------------------------------
50+
51+
def build_regexp_from_json_files(paths):
52+
expressions = []
53+
for p in paths:
54+
with file(p) as fp:
55+
expressions += json.load(fp)
56+
if len(expressions) is 0:
57+
return None
58+
return '(' + '|'.join(expressions) + ')'
59+
60+
exclude_re = None
61+
if 'exclude' in options:
62+
exclude_re = build_regexp_from_json_files(options['exclude'])
63+
if exclude_re is not None:
64+
exclude_re = re.compile(exclude_re)
65+
66+
include_re = None
67+
if 'include' in options:
68+
include_re = build_regexp_from_json_files(options['include'])
69+
if include_re is not None:
70+
include_re = re.compile(include_re)
71+
72+
def is_emterpreted(symbol):
73+
if include_re is not None:
74+
if include_re.search(symbol) is None:
75+
return False
76+
if exclude_re is not None:
77+
if exclude_re.search(symbol) is not None:
78+
return False
79+
return True
80+
81+
# Generate emterpreter whitelist
82+
# ------------------------------
83+
84+
# Run llvm-nm and yield symbol names from its standard output
85+
def iter_archive_symbols(archive):
86+
command = nm + ['-B', archive]
87+
if verbose:
88+
print(' '.join(command))
89+
output = subprocess.check_output(command)
90+
for line in output.splitlines():
91+
line = line.strip()
92+
93+
if len(line) is 0:
94+
# Empty line
95+
continue
96+
97+
if line.endswith(':'):
98+
# This line names a code object file (foo.o), so ignore it
99+
continue
100+
101+
# Split the line into elements. The layout of the line should
102+
# be something like: "[<address>] <type> <name>"
103+
line = line.split()
104+
105+
# Only "text" symbols, i.e. code defined in the current
106+
# object, should be emterpreted.
107+
if len(line) < 3 or line[1].lower() != 't':
108+
continue
109+
110+
# Put an "_" before each symbol to get the name as generated
111+
# by emscripten
112+
yield ('_' + line[2])
113+
114+
115+
# Generate list of all symbols that should be whitelisted
116+
object_path = options['input'][0]
117+
whitelist_symbols = []
118+
blacklist_symbols = []
119+
for symbol in iter_archive_symbols(object_path):
120+
if is_emterpreted(symbol):
121+
whitelist_symbols.append(symbol)
122+
else:
123+
blacklist_symbols.append(symbol)
124+
125+
print("Compiling {} functions".format(len(blacklist_symbols)))
126+
print("Emterpreting {} functions".format(len(whitelist_symbols)))
127+
128+
# Put into a JSON file
129+
if len(options['output']) > 0:
130+
json_path = options['output'][0]
131+
with file(json_path, 'w') as fp:
132+
json.dump(whitelist_symbols, fp, indent=4, separators=(',', ': '))
133+
if len(options['output']) > 1:
134+
json_path = options['output'][1]
135+
with file(json_path, 'w') as fp:
136+
json.dump(blacklist_symbols, fp, indent=4, separators=(',', ': '))

0 commit comments

Comments
 (0)