Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

emscripten_strict #4665

Merged
merged 18 commits into from
Dec 19, 2016
Merged

emscripten_strict #4665

merged 18 commits into from
Dec 19, 2016

Conversation

juj
Copy link
Collaborator

@juj juj commented Oct 24, 2016

This adds a new EMSCRIPTEN_STRICT build mode, enabled either with -s EMSCRIPTEN_STRICT=1 or with env. var EMSCRIPTEN_STRICT=1. That does the following things:

The intent is that these all would become deprecated options, and people would be able to use EMSCRIPTEN_STRICT mode to try out ahead of time that their projects build without relying on deprecated features.

In this PR, building without EMSCRIPTEN_STRICT mode doesn't yet change any existing behavior, but all changes are opt in at this point (although with the wording being about the intent to deprecate)

Tried to get as much of the breaking changes in one go to highlight/group the different things that I'd like to change. Note that these are all more or less validation related, where it would be nicest to be as strict as possible by default, like native compilers are.

@juj
Copy link
Collaborator Author

juj commented Oct 24, 2016

Need to run full suite before merging, but wanted to post this already for initial comments. Also needs a good heads up conversation on the mailing list, but let's first discuss here that the different deprecations are something that we want to agree on, and what would be the best way to bring those in. (In one go like this, or separately?)

@kripken
Copy link
Member

kripken commented Oct 24, 2016

Overall this sounds good. Main concerns are, as you said, to not surprise people and discuss everything well in advance, and yeah, a mailing list post to point to here makes sense.

This adds a new EMSCRIPTEN_STRICT build mode, enabled either with -s EMSCRIPTEN_STRICT=1 or with env. var EMSCRIPTEN_STRICT=1.

How about -s STRICT=1 for the option (seems odd to have EMSCRIPTEN_ in an option), and EMCC_STRICT=1 for the env var (others start with EMCC)?

@juj
Copy link
Collaborator Author

juj commented Oct 24, 2016

Alright. In the first version/sdk release, let's land this in a manner that does not remove any features, but just adds the strict mode. And in a later release, do the actual removal. That would give the kind of "two stage deprecate->remove" process to ensure not too abrupt changes. I'll post to the mailing list about this one to raise awareness.

if not use_js:
cmd += shared.EMSDK_OPTS + ['-D__EMSCRIPTEN__']
# The preprocessor define EMSCRIPTEN is deprecated. Don't pass it to code in strict mode. Code should use the define __EMSCRIPTEN__ instead.
if not os.environ.get('EMSCRIPTEN_STRICT') or int(os.environ.get('EMSCRIPTEN_STRICT')) == 0: cmd += ['-DEMSCRIPTEN']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nicer to have a variable strict_mode or such, that does the environment check once at the top of the file.

in fact maybe it could be in tools/shared.py, as others might need it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

COMPILER_OPTS = COMPILER_OPTS + ['-D__EMSCRIPTEN__']

# The preprocessor define EMSCRIPTEN is deprecated. Don't pass it to code in strict mode. Code should use the define __EMSCRIPTEN__ instead.
if not os.environ.get('EMSCRIPTEN_STRICT') or int(os.environ.get('EMSCRIPTEN_STRICT')) == 0: COMPILER_OPTS += ['-DEMSCRIPTEN']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yes, it is needed here. so maybe have Building.is_strict(), in this file, and we call it from all the places that need to ask if we are in strict mode?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added shared.is_emscripten_strict(). Could not put it in Building, because that is too late in the file for early bits of the Settings-related code to refer to it (and reordering would create other kind of dependency issue, with Building referring to Settings too early), so made it a self-standing function at the top of the file.

@kripken
Copy link
Member

kripken commented Oct 24, 2016

Meanwhile reading this a second time, I do see the logic in calling the option EMSCRIPTEN_STRICT - it's something about the behavior of emscripten itself, specifically. So I could go either way. Let's see what more people think.

@dschuff
Copy link
Member

dschuff commented Oct 25, 2016

+1, this is great; it should result in more consistent (and therefore hopefully less surprising) behavior, and allow us more implementation flexibility.
I think just STRICT is fine for the -s flag, and whatever is consistent with other env vars for that.

@juj
Copy link
Collaborator Author

juj commented Oct 25, 2016

I think it helps to have the linker flag -s EMSCRIPTEN_STRICT=1 coincide with the env. var name export EMSCRIPTEN_STRICT=1 so that one wouldn't need to rememeber two separate names for the same feature?

@kripken
Copy link
Member

kripken commented Oct 25, 2016

Well, this would be the first env var that has the exact same name as a setting - which opens up opportunities for confusion. Actually, though, why do we need both an env var and a setting here?

@juj
Copy link
Collaborator Author

juj commented Oct 25, 2016

Well, this would be the first env var that has the exact same name as a setting - which opens up opportunities for confusion. Actually, though, why do we need both an env var and a setting here?

I'd like to give as much flexibility as possible to be able to easily configure the feature to builds. Env. vars are great for locally/temporarily setting a value to try out once, but linker flags are better for build systems.

@kripken
Copy link
Member

kripken commented Oct 25, 2016

Are you suggesting we temporarily support the two ways, or forever?

@juj
Copy link
Collaborator Author

juj commented Oct 26, 2016

Ok, renamed to env. var EMCC_STRICT=1 and -s STRICT=1. I'm suggesting that we do support this env. var and linker flag permanently, but that the features that they add are not permanent. That is, the changes that strict mode adds now, e.g. no #define EMSCRIPTEN etc. would not be fixed to be things only in strict mode, but in the future what is now strict mode would be the default mode, so in a sense use strict mode as a "landing pad" to introduce deprecations (when there is no other flag to do so).

logging.warning('emcc: cannot find library "%s"', lib)
if not found:
# Some native libraries are implemented in Emscripten as system side JS libraries
js_system_libraries = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about refactoring this js system lib logic into Building in tools/shared.py?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is dependant on settings_changes before applying them, since it's filtering out which command line link items to accept/fail on. I'm not sure why it would be useful to separate it out?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it's not code likely to be used elsewhere. But just imagine adding a js library in the future, it feels nicer to edit a small function in shared.py rather than some code in the middle of emcc.py?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, moved the code to Building.

@@ -931,8 +935,10 @@ def get_llvm_target():

if LLVM_TARGET == WASM_TARGET:
# wasm target does not automatically define emscripten stuff, so do it here.
COMPILER_OPTS = COMPILER_OPTS + ['-DEMSCRIPTEN',
'-D__EMSCRIPTEN__']
COMPILER_OPTS = COMPILER_OPTS + ['-D__EMSCRIPTEN__']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this setting of the emscripten define is duplicated in emcc.py? if we need it in both (but do we?), perhaps we could refactor it into a shared method?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, the machinery touches the old EMCONFIGURE_JS hacks that are used to support the ./configure architecture. Looks like that mode does not have __EMSCRIPTEN__ set, as it shouldn't(?).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I see, the hacks there need to only add it in some cases.

I suppose we could have a add_emscripten_define method somewhere, which returns the one define or both depending on if we are in strict mode. How does that sound?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if adding that is much worth it, since this occurs exactly in two places, and it's quite lean to just add it in these locations. Eventually we'll be tearing down the code anyways when we get to remove #define EMSCRIPTEN altogether, so building a new abstraction for this seems temporary? Left this one as is for now, let me know if you feel strongly about this.

@@ -471,6 +471,10 @@ def find_temp_directory():
def get_emscripten_version(path):
return open(path).read().strip().replace('"', '')

# Returns true if Emscripten is running in 'strict' mode, in which deprecated compiler features are not supported.
def is_emscripten_strict():
return (os.environ.get('EMCC_STRICT') and int(os.environ.get('EMCC_STRICT')) != 0) or Settings.STRICT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can try to access Settings.STRICT before the Settings object exists, that worries me a lot.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In particular, we should have tests that check usage with both the env var and without, of things both working and not, so we hit that code path.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hit that case, and had to adapt.

@juj
Copy link
Collaborator Author

juj commented Oct 26, 2016

Pushed some new changes, namely made the browser suite to pass fully when running in EMCC_STRICT mode enabled. @kripken: could you take a fresh peek?

@juj
Copy link
Collaborator Author

juj commented Nov 19, 2016

Updated the PR and rebased to latest. This should be good to go now.

if not found:
system_js_libraries += shared.Building.path_to_system_js_libraries(lib)

# Certain linker flags imply some link libraries to be pulled in by default.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i still feel these lines are nicer in something like shared.Building.add_default_system_libraries_from_settings_changes (or a better name ;) ), but i don't feel strongly about it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe leave that for a possible followup.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, added a function for that in Building. Not sure if I managed a better name though :)

$FS__deps: ['$ERRNO_CODES', '$ERRNO_MESSAGES', '__setErrNo', '$PATH', '$TTY', '$MEMFS', '$IDBFS', '$NODEFS', '$WORKERFS', 'stdin', 'stdout', 'stderr'],
$FS__deps: ['$ERRNO_CODES', '$ERRNO_MESSAGES', '__setErrNo', '$PATH', '$TTY', '$MEMFS',
#if __EMSCRIPTEN_HAS_idbfs_js__
'$IDBFS',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation not consistent with the following ones

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

// building with -s ERROR_ON_MISSING_LIBRARIES=0 setting,
// prefer to set that option explicitly in your build system.

var SYSTEM_JS_LIBRARIES = []; // Specifies a list of Emscripten-provided JS libraries to link against.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently we have js_libraries in emcc.py, which invokes emscripten.py with --libraries. It seems like emcc.py could use this new variable, and we could get rid of --libraries? That would avoid duplicated functionality.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although for people using emscripten.py directly, maybe we shouldn't remove that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that might be true, though not sure if I want to change this right now at least, there doesn't seem to be any tests for the --libraries flag in our test suite.

@@ -472,6 +472,15 @@ def find_temp_directory():
def get_emscripten_version(path):
return open(path).read().strip().replace('"', '')

# Returns true if Emscripten is running in 'strict' mode, in which deprecated compiler features are not supported.
def is_emscripten_strict():
if os.environ.get('EMCC_STRICT') and int(os.environ.get('EMCC_STRICT')) != 0: return True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this int operation may throw, if the value is e.g. a. instead, please do os.environ.get('EMCC_STRICT') != '0'

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's good to throw, no? That would indicate someone messed up setting the env. var, so a critical abort there would make sense, as opposed to e.g. silently treating export EMCC_STRICT=a as falsey or something similar.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throwing might be ok, but it'll throw something like ValueError: invalid literal for int() with base 10 which is not very clear. and in all other places, we accept '0' as "no", and everything else, including "a" as yes.

'-Dunix',
'-D__unix',
'-D__unix__']

# The preprocessor define EMSCRIPTEN is deprecated. Don't pass it to code in strict mode. Code should use the define __EMSCRIPTEN__ instead.
if not is_emscripten_strict(): COMPILER_OPTS += ['-DEMSCRIPTEN']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we discussed this already - this code happens before the STRICT setting is applied. so only the env var can influence this I think? ditto below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's right. Revised the code flow for this now.

@kripken
Copy link
Member

kripken commented Nov 21, 2016

Some testing comments:

  • I see you got the test suite to pass with or without STRICT? Is there a test that verifies the old behavior, e.g. a GL test that doesn't -lGL?
  • We need direct tests for the strict mode features, checking both the env and the setting, in test_other.py.

@juj
Copy link
Collaborator Author

juj commented Nov 22, 2016

I see you got the test suite to pass with or without STRICT? Is there a test that verifies the old behavior, e.g. a GL test that doesn't -lGL?

Added test browser.test_sdl1_in_emscripten_nonstrict_mode to verify that code still passes with the old behavior.

We need direct tests for the strict mode features, checking both the env and the setting, in test_other.py.

Added test other.test_error_on_missing_libraries to verify that the strict mode applies correctly on both command line and env. var, and that the defaults for strict mode for this specific flag are as expected.

@juj
Copy link
Collaborator Author

juj commented Nov 22, 2016

When testing, I realize that the preprocessor define check goes all the way to emscripten-core/emscripten-fastcomp-clang#11

@kripken
Copy link
Member

kripken commented Nov 22, 2016

Thanks, lgtm aside from that tiny test thing.

@kripken
Copy link
Member

kripken commented Nov 22, 2016

Oh and the '0' thing.

@juj
Copy link
Collaborator Author

juj commented Dec 7, 2016

Changed the '0' thing and rebased to latest. This would be good to merge in before merge to master. Existing behavior should not be changed in any way, so should be safe to add in.

@juj
Copy link
Collaborator Author

juj commented Dec 16, 2016

@kripken: Does this look good to you to merge? I'd really like to get this in to 1.37.0, because this adds the new -s ERROR_ON_MISSING_LIBRARIES=0/1 option so people will have an ability to turn that one on already.

@juj juj mentioned this pull request Dec 16, 2016
@@ -918,6 +922,24 @@ def detect_fixed_language_mode(args):
if separate_asm:
shared.Settings.SEPARATE_ASM = os.path.basename(asm_target)

if 'EMCC_STRICT' in os.environ:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this happens after the code above for -DEMSCRIPTEN, so it seems it would only work for the setting and not the environment var?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or wait, is the above for the configure stuff? then it's ok.

if 'EMCC_STRICT' in os.environ:
shared.Settings.STRICT = os.environ.get('EMCC_STRICT') != '0'

STRICT = ([None] + filter(lambda x: x.startswith('STRICT='), settings_changes))[-1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why [None]?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I guess it doesn't matter much, let me go with False instead since it's evaluated to true/false below.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I wasn't clear. My issue isn't with None vs False. It's that I don't understand that line - why does it need [..something..] at all? I guess it's so the -1 can work, because we happen to just want the last one but the line just seems overcomplicated.

and we do this twice. how about adding a method, get_last_change which returns the last change, or None if there isn't one?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh, I see. Refactored that to its own function, that looks better now, what do you think?

shared.Settings.STRICT = os.environ.get('EMCC_STRICT') != '0'

STRICT = ([None] + filter(lambda x: x.startswith('STRICT='), settings_changes))[-1]
if STRICT:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I worry about creating a var called STRICT, might confuse us in the future as to what to use. How about temp_strict or strict_changes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, opting for strict_cmdline since the variable comes from a command line setting.


STRICT = ([None] + filter(lambda x: x.startswith('STRICT='), settings_changes))[-1]
if STRICT:
shared.Settings.STRICT = int(STRICT[len('STRICT='):])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The settings changes would set Settings.STRICT for you - why do we even need to do this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see, because we need to do the library stuff first.

But then, let's comment that above here and not just below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check.

shared.Settings.ERROR_ON_MISSING_LIBRARIES = 1

# Libraries are searched before settings_changes are applied, so pull its value from the command line already here.
ERROR_ON_MISSING_LIBRARIES = ([None] + filter(lambda x: x.startswith('ERROR_ON_MISSING_LIBRARIES='), settings_changes))[-1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, why [None]?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to False as well.

# Libraries are searched before settings_changes are applied, so apply the value for STRICT and ERROR_ON_MISSING_LIBRARIES from
# command line already now.

def last_element_or_none(arr):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, but let's share even more code here. this is only used on the settings_changes array. so you can call this get_last_settings_change, and receive the name of the pref as a param

lgtm with that. might be some more cleanup worth doing, but we can do it in followups.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh ok, updated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants