Merge pull request #1095 from centricular/llvm-ir-assembly

Implement support for LLVM IR compilation
pull/832/head
Jussi Pakkanen 8 years ago committed by GitHub
commit e128d26b35
  1. 84
      mesonbuild/backend/ninjabackend.py
  2. 18
      mesonbuild/compilers.py
  3. 6
      mesonbuild/environment.py
  4. 9
      test cases/common/126 llvm ir and assembly/main.c
  5. 12
      test cases/common/126 llvm ir and assembly/main.cpp
  6. 17
      test cases/common/126 llvm ir and assembly/meson.build
  7. 9
      test cases/common/126 llvm ir and assembly/square-arm.S
  8. 9
      test cases/common/126 llvm ir and assembly/square-x86.S
  9. 9
      test cases/common/126 llvm ir and assembly/square-x86_64.S
  10. 4
      test cases/common/126 llvm ir and assembly/square.ll
  11. 5
      test cases/common/126 llvm ir and assembly/symbol-underscore.h
  12. 16
      test cases/common/127 cpp and asm/meson.build
  13. 8
      test cases/common/127 cpp and asm/retval-arm.S
  14. 8
      test cases/common/127 cpp and asm/retval-x86.S
  15. 8
      test cases/common/127 cpp and asm/retval-x86_64.S
  16. 5
      test cases/common/127 cpp and asm/symbol-underscore.h
  17. 16
      test cases/common/127 cpp and asm/trivial.cc

@ -257,10 +257,14 @@ int dummy;
# Languages that can mix with C or C++ but don't support unity builds yet
# because the syntax we use for unity builds is specific to C/++/ObjC/++.
# Assembly files cannot be unitified and neither can LLVM IR files
langs_cant_unity = ('d', 'fortran')
def get_target_source_can_unity(self, target, source):
if isinstance(source, File):
source = source.fname
if self.environment.is_llvm_ir(source) or \
self.environment.is_assembly(source):
return False
suffix = os.path.splitext(source)[1][1:]
for lang in self.langs_cant_unity:
if not lang in target.compilers:
@ -374,6 +378,9 @@ int dummy;
# because we need `header_deps` to be fully generated in the above loop.
for src in generated_source_files:
src_list.append(src)
if self.environment.is_llvm_ir(src):
obj_list.append(self.generate_llvm_ir_compile(target, outfile, src))
continue
obj_list.append(self.generate_single_compile(target, outfile, src, True,
header_deps=header_deps))
@ -409,7 +416,9 @@ int dummy;
for f, src in target_sources.items():
if not self.environment.is_header(src):
src_list.append(src)
if is_unity and self.get_target_source_can_unity(target, src):
if self.environment.is_llvm_ir(src):
obj_list.append(self.generate_llvm_ir_compile(target, outfile, src))
elif is_unity and self.get_target_source_can_unity(target, src):
abs_src = os.path.join(self.environment.get_build_dir(),
src.rel_to_builddir(self.build_to_src))
unity_src.append(abs_src)
@ -1365,6 +1374,36 @@ rule FORTRAN_DEP_HACK
'''
outfile.write(template % cmd)
def generate_llvm_ir_compile_rule(self, compiler, is_cross, outfile):
if getattr(self, 'created_llvm_ir_rule', False):
return
rule = 'rule llvm_ir{}_COMPILER\n'.format('_CROSS' if is_cross else '')
args = [' '.join([ninja_quote(i) for i in compiler.get_exelist()]),
' '.join(self.get_cross_info_lang_args(compiler, is_cross)),
' '.join(compiler.get_output_args('$out')),
' '.join(compiler.get_compile_only_args())]
if mesonlib.is_windows():
command_template = ' command = {} @$out.rsp\n' \
' rspfile = $out.rsp\n' \
' rspfile_content = {} $ARGS {} {} $in\n'
else:
command_template = ' command = {} {} $ARGS {} {} $in\n'
command = command_template.format(*args)
description = ' description = Compiling LLVM IR object $in.\n'
outfile.write(rule)
outfile.write(command)
outfile.write(description)
outfile.write('\n')
self.created_llvm_ir_rule = True
def get_cross_info_lang_args(self, lang, is_cross):
if is_cross:
try:
return self.environment.cross_info.config['properties'][lang + '_args']
except KeyError:
pass
return []
def generate_compile_rule_for(self, langname, compiler, qstr, is_cross, outfile):
if langname == 'java':
if not is_cross:
@ -1399,12 +1438,7 @@ rule FORTRAN_DEP_HACK
if d != '$out' and d != '$in':
d = qstr % d
quoted_depargs.append(d)
cross_args = []
if is_cross:
try:
cross_args = self.environment.cross_info.config['properties'][langname + '_args']
except KeyError:
pass
cross_args = self.get_cross_info_lang_args(langname, is_cross)
if mesonlib.is_windows():
command_template = ''' command = %s @$out.rsp
rspfile = $out.rsp
@ -1477,6 +1511,8 @@ rule FORTRAN_DEP_HACK
qstr = quote_char + "%s" + quote_char
for compiler in self.build.compilers:
langname = compiler.get_language()
if compiler.get_id() == 'clang':
self.generate_llvm_ir_compile_rule(compiler, False, outfile)
self.generate_compile_rule_for(langname, compiler, qstr, False, outfile)
self.generate_pch_rule_for(langname, compiler, qstr, False, outfile)
if self.environment.is_cross_build():
@ -1488,6 +1524,8 @@ rule FORTRAN_DEP_HACK
cclist = self.build.compilers
for compiler in cclist:
langname = compiler.get_language()
if compiler.get_id() == 'clang':
self.generate_llvm_ir_compile_rule(compiler, True, outfile)
self.generate_compile_rule_for(langname, compiler, qstr, True, outfile)
self.generate_pch_rule_for(langname, compiler, qstr, True, outfile)
outfile.write('\n')
@ -1680,9 +1718,39 @@ rule FORTRAN_DEP_HACK
def get_link_debugfile_args(self, linker, target, outname):
return linker.get_link_debugfile_args(outname)
def generate_llvm_ir_compile(self, target, outfile, src):
compiler = get_compiler_for_source(target.compilers.values(), src)
commands = []
# Compiler args for compiling this target
commands += compilers.get_base_compile_args(self.environment.coredata.base_options,
compiler)
if isinstance(src, (RawFilename, File)):
src_filename = src.fname
elif os.path.isabs(src):
src_filename = os.path.basename(src)
else:
src_filename = src
obj_basename = src_filename.replace('/', '_').replace('\\', '_')
rel_obj = os.path.join(self.get_target_private_dir(target), obj_basename)
rel_obj += '.' + self.environment.get_object_suffix()
commands += self.get_compile_debugfile_args(compiler, target, rel_obj)
if isinstance(src, RawFilename):
rel_src = src.fname
elif isinstance(src, File):
rel_src = src.rel_to_builddir(self.build_to_src)
else:
raise InvalidArguments('Invalid source type: {!r}'.format(src))
# Write the Ninja build command
compiler_name = 'llvm_ir{}_COMPILER'.format('_CROSS' if target.is_cross else '')
element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src)
commands = self.dedup_arguments(commands)
element.add_item('ARGS', commands)
element.write(outfile)
return rel_obj
def generate_single_compile(self, target, outfile, src, is_generated=False, header_deps=[], order_deps=[]):
"""
Compiles C/C++, ObjC/ObjC++, and D sources
Compiles C/C++, ObjC/ObjC++, Fortran, and D sources
"""
if isinstance(src, str) and src.endswith('.h'):
raise AssertionError('BUG: sources should not contain headers')

@ -59,6 +59,16 @@ def is_source(fname):
suffix = fname.split('.')[-1]
return suffix in clike_suffixes
def is_assembly(fname):
if hasattr(fname, 'fname'):
fname = fname.fname
return fname.split('.')[-1].lower() == 's'
def is_llvm_ir(fname):
if hasattr(fname, 'fname'):
fname = fname.fname
return fname.split('.')[-1] == 'll'
def is_object(fname):
if hasattr(fname, 'fname'):
fname = fname.fname
@ -2018,6 +2028,8 @@ class GnuCompiler:
if self.gcc_type != GCC_OSX:
self.base_options.append('b_lundef')
self.base_options.append('b_asneeded')
# All GCC backends can do assembly
self.can_compile_suffixes.add('s')
def get_colorout_args(self, colortype):
if mesonlib.version_compare(self.version, '>=4.9.0'):
@ -2071,8 +2083,6 @@ class GnuCCompiler(GnuCompiler, CCompiler):
def __init__(self, exelist, version, gcc_type, is_cross, exe_wrapper=None, defines=None):
CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper)
GnuCompiler.__init__(self, gcc_type, defines)
# Gcc can do asm, too.
self.can_compile_suffixes.add('s')
self.warn_args = {'1': ['-Wall', '-Winvalid-pch'],
'2': ['-Wall', '-Wextra', '-Winvalid-pch'],
'3' : ['-Wall', '-Wpedantic', '-Wextra', '-Winvalid-pch']}
@ -2185,6 +2195,8 @@ class ClangCompiler():
if self.clang_type != CLANG_OSX:
self.base_options.append('b_lundef')
self.base_options.append('b_asneeded')
# All Clang backends can do assembly and LLVM IR
self.can_compile_suffixes.update(['ll', 's'])
def get_pic_args(self):
if self.clang_type in (CLANG_WIN, CLANG_OSX):
@ -2240,8 +2252,6 @@ class ClangCCompiler(ClangCompiler, CCompiler):
def __init__(self, exelist, version, clang_type, is_cross, exe_wrapper=None):
CCompiler.__init__(self, exelist, version, is_cross, exe_wrapper)
ClangCompiler.__init__(self, clang_type)
# Clang can do asm, too.
self.can_compile_suffixes.add('s')
self.warn_args = {'1': ['-Wall', '-Winvalid-pch'],
'2': ['-Wall', '-Wextra', '-Winvalid-pch'],
'3' : ['-Wall', '-Wpedantic', '-Wextra', '-Winvalid-pch']}

@ -271,6 +271,12 @@ class Environment():
def is_source(self, fname):
return is_source(fname)
def is_assembly(self, fname):
return is_assembly(fname)
def is_llvm_ir(self, fname):
return is_llvm_ir(fname)
def is_object(self, fname):
return is_object(fname)

@ -0,0 +1,9 @@
unsigned square_unsigned (unsigned a);
int
main (int argc, char * argv[])
{
if (square_unsigned (2) != 4)
return -1;
return 0;
}

@ -0,0 +1,12 @@
extern "C" {
unsigned square_unsigned (unsigned a);
}
int
main (int argc, char * argv[])
{
if (square_unsigned (2) != 4)
return -1;
return 0;
}

@ -0,0 +1,17 @@
project('llvm-ir', 'c', 'cpp')
foreach lang : ['c', 'cpp']
cc_id = meson.get_compiler(lang).get_id()
if cc_id == 'clang'
e = executable('square_ir_' + lang, 'square.ll', 'main.' + lang)
test('test IR square' + lang, e)
endif
# FIXME: msvc does not support passing assembly to cl.exe, but you can pass
# it to ml.exe and get a compiled object. Meson should add support for
# transparently building assembly with ml.exe with MSVC.
if cc_id != 'msvc'
cpu = host_machine.cpu_family()
e = executable('square_asm_' + lang, 'square-' + cpu + '.S', 'main.' + lang)
test('test ASM square' + lang, e, args : [e.full_path()])
endif
endforeach

@ -0,0 +1,9 @@
#include "symbol-underscore.h"
.text
.globl SYMBOL_NAME(square_unsigned)
SYMBOL_NAME(square_unsigned):
mul r1, r0, r0
mov r0, r1
mov pc, lr

@ -0,0 +1,9 @@
#include "symbol-underscore.h"
.text
.globl SYMBOL_NAME(square_unsigned)
SYMBOL_NAME(square_unsigned):
movl 4(%esp), %eax
imull %eax, %eax
retl

@ -0,0 +1,9 @@
#include "symbol-underscore.h"
.text
.globl SYMBOL_NAME(square_unsigned)
SYMBOL_NAME(square_unsigned):
imull %edi, %edi
movl %edi, %eax
retq

@ -0,0 +1,4 @@
define i32 @square_unsigned(i32 %a) {
%1 = mul i32 %a, %a
ret i32 %1
}

@ -0,0 +1,5 @@
#if defined(__WIN32__) || defined(__APPLE__)
# define SYMBOL_NAME(name) _##name
#else
# define SYMBOL_NAME(name) name
#endif

@ -0,0 +1,16 @@
project('c++ and assembly test', 'cpp')
sources = ['trivial.cc']
# If the compiler cannot compile assembly, don't use it
if meson.get_compiler('cpp').get_id() != 'msvc'
cpu = host_machine.cpu_family()
sources += ['retval-' + cpu + '.S']
cpp_args = ['-DUSE_ASM']
message('Using ASM')
else
cpp_args = ['-DNO_USE_ASM']
endif
exe = executable('trivialprog', sources,
cpp_args : cpp_args)
test('runtest', exe)

@ -0,0 +1,8 @@
#include "symbol-underscore.h"
.text
.globl SYMBOL_NAME(get_retval)
SYMBOL_NAME(get_retval):
mov r0, #0
mov pc, lr

@ -0,0 +1,8 @@
#include "symbol-underscore.h"
.text
.globl SYMBOL_NAME(get_retval)
SYMBOL_NAME(get_retval):
xorl %eax, %eax
retl

@ -0,0 +1,8 @@
#include "symbol-underscore.h"
.text
.globl SYMBOL_NAME(get_retval)
SYMBOL_NAME(get_retval):
xorl %eax, %eax
retq

@ -0,0 +1,5 @@
#if defined(__WIN32__) || defined(__APPLE__)
# define SYMBOL_NAME(name) _##name
#else
# define SYMBOL_NAME(name) name
#endif

@ -0,0 +1,16 @@
#include<iostream>
extern "C" {
int get_retval(void);
}
int main(int argc, char **argv) {
std::cout << "C++ seems to be working." << std::endl;
#if defined(USE_ASM)
return get_retval();
#elif defined(NO_USE_ASM)
return 0;
#else
#error "Forgot to pass asm define"
#endif
}
Loading…
Cancel
Save