Skip to content

Commit

Permalink
[emcc.py] Simplify passing arguments to clang in compile only mode
Browse files Browse the repository at this point in the history
This change avoids removing the `-o` flag and the input files when only
compiling.  We delay the splitting out of the linker flags and the input
files until we know actually do to do linking.

This effect of this is that the compile-only phase is now much simply
because it doesn't need to re-inject the input files and the `-o` flag.

This also as the effect of keeping the order of those flags preserved
with respect to other command line flags when calling clang to do
compilation.
  • Loading branch information
sbc100 committed Jan 18, 2025
1 parent 01e34f5 commit 6ea0733
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 49 deletions.
91 changes: 44 additions & 47 deletions emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ class EmccOptions:
def __init__(self):
self.target = ''
self.output_file = None
self.input_files = []
self.no_minify = False
self.post_link = False
self.save_temps = False
Expand Down Expand Up @@ -659,21 +660,23 @@ def run(args):
# settings until we reach the linking phase.
settings.limit_settings(COMPILE_TIME_SETTINGS)

newargs, input_files = phase_setup(options, state, newargs)
phase_setup(options, state, newargs)

if options.reproduce:
create_reproduce_file(options.reproduce, args)

if state.mode == Mode.POST_LINK_ONLY:
if len(input_files) != 1:
if len(options.input_files) != 1:
exit_with_error('--post-link requires a single input file')
split_linker_flags(options, state, newargs)
# Delay import of link.py to avoid processing this file when only compiling
from tools import link
link.run_post_link(input_files[0][1], options, state)
link.run_post_link(options.input_files[0], options, state)
return 0

## Compile source code to object files
linker_inputs = phase_compile_inputs(options, state, newargs, input_files)
# Compile source code to object files
# When only compiling this function never returns.
linker_inputs = phase_compile_inputs(options, state, newargs)

if state.mode == Mode.COMPILE_AND_LINK:
# Delay import of link.py to avoid processing this file when only compiling
Expand Down Expand Up @@ -711,11 +714,13 @@ def phase_parse_arguments(state):
settings.WARN_DEPRECATED = 0

for i in range(len(newargs)):
if newargs[i] in ('-l', '-L', '-I', '-z', '--js-library'):
if newargs[i] in ('-l', '-L', '-I', '-z', '--js-library', '-o'):
# Scan for flags that can be written as either one or two arguments
# and normalize them to the single argument form.
if newargs[i] == '--js-library':
newargs[i] += '='
if len(newargs) <= i + 1:
exit_with_error(f"option '{newargs[i]}' requires an argument")
newargs[i] += newargs[i + 1]
newargs[i + 1] = ''

Expand Down Expand Up @@ -748,10 +753,8 @@ def phase_parse_arguments(state):
return options, newargs


@ToolchainProfiler.profile_block('setup')
def phase_setup(options, state, newargs):
"""Second phase: configure and setup the compiler based on the specified settings and arguments.
"""
def split_linker_flags(options, state, newargs):
newargs = list(newargs)

if settings.RUNTIME_LINKED_LIBS:
newargs += settings.RUNTIME_LINKED_LIBS
Expand All @@ -769,7 +772,6 @@ def phase_setup(options, state, newargs):
# based on a full understanding of gcc params, right now we just assume that
# what is left contains no more |-x OPT| things
skip = False
has_header_inputs = False
for i in range(len(newargs)):
if skip:
skip = False
Expand All @@ -787,10 +789,9 @@ def phase_setup(options, state, newargs):
# https://bugs.python.org/issue1311
if not os.path.exists(arg) and arg != os.devnull:
exit_with_error('%s: No such file or directory ("%s" was expected to be an input file, based on the commandline arguments provided)', arg, arg)
file_suffix = get_file_suffix(arg)
if file_suffix in HEADER_ENDINGS:
has_header_inputs = True
input_files.append((i, arg))
elif arg.startswith('-o'):
newargs[i] = ''
elif arg.startswith('-L'):
state.add_link_flag(i, arg)
elif arg.startswith('-l'):
Expand Down Expand Up @@ -819,9 +820,15 @@ def phase_setup(options, state, newargs):

newargs = [a for a in newargs if a]

# SSEx is implemented on top of SIMD128 instruction set, but do not pass SSE flags to LLVM
# so it won't think about generating native x86 SSE code.
newargs = [x for x in newargs if x not in SIMD_INTEL_FEATURE_TOWER and x not in SIMD_NEON_FLAGS]
return newargs, input_files


@ToolchainProfiler.profile_block('setup')
def phase_setup(options, state, newargs):
"""Second phase: configure and setup the compiler based on the specified settings and arguments.
"""

has_header_inputs = any(get_file_suffix(f) in HEADER_ENDINGS for f in options.input_files)

if options.post_link:
state.mode = Mode.POST_LINK_ONLY
Expand Down Expand Up @@ -923,8 +930,6 @@ def phase_setup(options, state, newargs):
if settings.USE_SDL == 2 or settings.USE_SDL_MIXER == 2 or settings.USE_SDL_GFX == 2:
default_setting('GL_ENABLE_GET_PROC_ADDRESS', 1)

return (newargs, input_files)


def filter_out_link_flags(args):
rtn = []
Expand All @@ -949,7 +954,7 @@ def is_link_flag(flag):


@ToolchainProfiler.profile_block('compile inputs')
def phase_compile_inputs(options, state, compile_args, input_files):
def phase_compile_inputs(options, state, newargs):
if shared.run_via_emxx:
compiler = [shared.CLANG_CXX]
else:
Expand All @@ -973,24 +978,21 @@ def get_language_mode(args):
return removeprefix(item, '-x')
return ''

language_mode = get_language_mode(compile_args)
language_mode = get_language_mode(newargs)
use_cxx = 'c++' in language_mode or shared.run_via_emxx

def get_clang_command():
return compiler + get_cflags(state.orig_args, use_cxx) + compile_args
return compiler + get_cflags(state.orig_args, use_cxx)

def get_clang_command_preprocessed():
return compiler + get_clang_flags(state.orig_args) + compile_args
return compiler + get_clang_flags(state.orig_args)

def get_clang_command_asm():
return compiler + get_target_flags() + compile_args
return compiler + get_target_flags()

# preprocessor-only (-E/-M) support
if state.mode == Mode.PREPROCESS_ONLY:
inputs = [i[1] for i in input_files]
cmd = get_clang_command() + inputs
if options.output_file:
cmd += ['-o', options.output_file]
cmd = get_clang_command() + newargs
# Do not compile, but just output the result from preprocessing stage or
# output the dependency rule. Warning: clang and gcc behave differently
# with -MF! (clang seems to not recognize it)
Expand All @@ -1000,34 +1002,26 @@ def get_clang_command_asm():

# Precompiled headers support
if state.mode == Mode.PCH:
inputs = [i[1] for i in input_files]
for header in inputs:
if shared.suffix(header) not in HEADER_ENDINGS:
exit_with_error(f'cannot mix precompiled headers with non-header inputs: {inputs} : {header}')
cmd = get_clang_command() + inputs
if options.output_file:
cmd += ['-o', options.output_file]
cmd = get_clang_command() + newargs
logger.debug(f"running (for precompiled headers): {cmd[0]} {' '.join(cmd[1:])}")
shared.exec_process(cmd)
assert False, 'exec_process does not return'

if state.mode == Mode.COMPILE_ONLY:
inputs = [i[1] for i in input_files]
if all(get_file_suffix(i) in ASSEMBLY_ENDINGS for i in inputs):
cmd = get_clang_command_asm() + inputs
if options.output_file and get_file_suffix(options.output_file) == '.bc' and not settings.LTO and '-emit-llvm' not in state.orig_args:
diagnostics.warning('emcc', '.bc output file suffix used without -flto or -emit-llvm. Consider using .o extension since emcc will output an object file, not a bitcode file')
if all(get_file_suffix(i) in ASSEMBLY_ENDINGS for i in options.input_files):
cmd = get_clang_command_asm() + newargs
else:
cmd = get_clang_command() + inputs
if options.output_file:
cmd += ['-o', options.output_file]
if get_file_suffix(options.output_file) == '.bc' and not settings.LTO and '-emit-llvm' not in state.orig_args:
diagnostics.warning('emcc', '.bc output file suffix used without -flto or -emit-llvm. Consider using .o extension since emcc will output an object file, not a bitcode file')
cmd = get_clang_command() + newargs
shared.exec_process(cmd)
assert False, 'exec_process does not return'

# In COMPILE_AND_LINK we need to compile source files too, but we also need to
# filter out the link flags
assert state.mode == Mode.COMPILE_AND_LINK
assert not options.dash_c
compile_args, input_files = split_linker_flags(options, state, newargs)
compile_args = filter_out_link_flags(compile_args)
linker_inputs = []
seen_names = {}
Expand All @@ -1052,7 +1046,7 @@ def compile_source_file(i, input_file):
cmd = get_clang_command()
if get_file_suffix(input_file) in ['.pcm']:
cmd = [c for c in cmd if not c.startswith('-fprebuilt-module-path=')]
cmd += ['-c', input_file, '-o', output_file]
cmd += compile_args + ['-c', input_file, '-o', output_file]
if state.mode == Mode.COMPILE_AND_LINK and options.requested_debug == '-gsplit-dwarf':
# When running in COMPILE_AND_LINK mode we compile to temporary location
# but we want the `.dwo` file to be generated in the current working directory,
Expand Down Expand Up @@ -1476,11 +1470,8 @@ def consume_arg_file():
options.shared = True
elif check_flag('-r'):
options.relocatable = True
elif check_arg('-o'):
options.output_file = consume_arg()
elif arg.startswith('-o'):
options.output_file = removeprefix(arg, '-o')
newargs[i] = ''
elif check_arg('-target') or check_arg('--target'):
options.target = consume_arg()
if options.target not in ('wasm32', 'wasm64', 'wasm64-unknown-emscripten', 'wasm32-unknown-emscripten'):
Expand All @@ -1497,6 +1488,12 @@ def consume_arg_file():
options.dash_M = True
elif arg == '-fsyntax-only':
options.syntax_only = True
elif arg in SIMD_INTEL_FEATURE_TOWER or arg in SIMD_NEON_FLAGS:
# SSEx is implemented on top of SIMD128 instruction set, but do not pass SSE flags to LLVM
# so it won't think about generating native x86 SSE code.
newargs[i] = ''
elif arg and (arg == '-' or not arg.startswith('-')):
options.input_files.append(arg)

if should_exit:
sys.exit(0)
Expand Down
4 changes: 2 additions & 2 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -12624,8 +12624,8 @@ def test_default_to_cxx(self):
self.assertContained("'string' file not found", err)

# But it works if we pass and explicit language mode.
self.run_process([EMCC, '-c', 'cxxfoo.h', '-x', 'c++-header'])
self.run_process([EMCC, '-c', 'cxxfoo.h', '-x', 'c++'])
self.run_process([EMCC, '-x', 'c++-header', '-c', 'cxxfoo.h'])
self.run_process([EMCC, '-x', 'c++', '-c', 'cxxfoo.h'])

@parameterized({
'': ([],),
Expand Down

0 comments on commit 6ea0733

Please sign in to comment.