1# Licensed to the Apache Software Foundation (ASF) under one
2# or more contributor license agreements.  See the NOTICE file
3# distributed with this work for additional information
4# regarding copyright ownership.  The ASF licenses this file
5# to you under the Apache License, Version 2.0 (the
6# "License"); you may not use this file except in compliance
7# with the License.  You may obtain a copy of the License at
8#
9#   http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing,
12# software distributed under the License is distributed on an
13# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14# KIND, either express or implied.  See the License for the
15# specific language governing permissions and limitations
16# under the License.
17"""Util to invoke C/C++ compilers in the system."""
18# pylint: disable=invalid-name
19from __future__ import absolute_import as _abs
20import sys
21import subprocess
22import os
23
24from .._ffi.base import py_str
25from .util import tempdir
26
27def create_shared(output,
28                  objects,
29                  options=None,
30                  cc="g++"):
31    """Create shared library.
32
33    Parameters
34    ----------
35    output : str
36        The target shared library.
37
38    objects : List[str]
39        List of object files.
40
41    options : List[str]
42        The list of additional options string.
43
44    cc : Optional[str]
45        The compiler command.
46    """
47    if sys.platform == "darwin" or sys.platform.startswith("linux"):
48        _linux_compile(output, objects, options, cc)
49    elif sys.platform == "win32":
50        _windows_shared(output, objects, options)
51    else:
52        raise ValueError("Unsupported platform")
53
54
55# assign so as default output format
56create_shared.output_format = "so" if sys.platform != "win32" else "dll"
57
58
59def build_create_shared_func(options=None, compile_cmd="g++"):
60    """Build create_shared function with particular default options and compile_cmd.
61
62    Parameters
63    ----------
64    options : List[str]
65        The list of additional options string.
66
67    compile_cmd : Optional[str]
68        The compiler command.
69
70    Returns
71    -------
72    create_shared_wrapper : Callable[[str, str, Optional[str]], None]
73        A compilation function that can be passed to export_library or to autotvm.LocalBuilder.
74    """
75    def create_shared_wrapper(output, objects, options=options, compile_cmd=compile_cmd):
76        create_shared(output, objects, options, compile_cmd)
77    create_shared_wrapper.output_format = create_shared.output_format
78    return create_shared_wrapper
79
80
81def cross_compiler(compile_func, base_options=None, output_format="so"):
82    """Create a cross compiler function.
83
84    Parameters
85    ----------
86    compile_func : Callable[[str, str, Optional[str]], None]
87        Function that performs the actual compilation
88
89    base_options : Optional[List[str]]
90        List of additional optional string.
91
92    output_format : Optional[str]
93        Library output format.
94
95    Returns
96    -------
97    fcompile : Callable[[str, str, Optional[str]], None]
98        A compilation function that can be passed to export_library.
99    """
100    if base_options is None:
101        base_options = []
102    def _fcompile(outputs, objects, options=None):
103        all_options = base_options
104        if options is not None:
105            all_options += options
106        compile_func(outputs, objects, options=all_options)
107    _fcompile.output_format = output_format
108    return _fcompile
109
110
111def _linux_compile(output, objects, options, compile_cmd="g++"):
112    cmd = [compile_cmd]
113    if output.endswith(".so") or output.endswith(".dylib"):
114        cmd += ["-shared", "-fPIC"]
115        if sys.platform == "darwin":
116            cmd += ["-undefined", "dynamic_lookup"]
117    elif output.endswith(".obj"):
118        cmd += ["-c"]
119    cmd += ["-o", output]
120    if isinstance(objects, str):
121        cmd += [objects]
122    else:
123        cmd += objects
124    if options:
125        cmd += options
126    proc = subprocess.Popen(
127        cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
128    (out, _) = proc.communicate()
129    if proc.returncode != 0:
130        msg = "Compilation error:\n"
131        msg += py_str(out)
132        raise RuntimeError(msg)
133
134
135def _windows_shared(output, objects, options):
136    cl_cmd = ["cl"]
137    cl_cmd += ["-c"]
138    if isinstance(objects, str):
139        objects = [objects]
140    cl_cmd += objects
141    if options:
142        cl_cmd += options
143
144    temp = tempdir()
145    dllmain_path = temp.relpath("dllmain.cc")
146    with open(dllmain_path, "w") as dllmain_obj:
147        dllmain_obj.write('#include <windows.h>\
148BOOL APIENTRY DllMain( HMODULE hModule,\
149                       DWORD  ul_reason_for_call,\
150                       LPVOID lpReserved)\
151{return TRUE;}')
152
153    cl_cmd += [dllmain_path]
154
155    temp_path = dllmain_path.replace("dllmain.cc", "")
156    cl_cmd += ["-Fo:" + temp_path]
157    try:
158        proc = subprocess.Popen(
159            cl_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
160        (out, _) = proc.communicate()
161    except FileNotFoundError:
162        raise RuntimeError("Can not find cl.exe,"
163                           "please run this in Vistual Studio Command Prompt.")
164    if proc.returncode != 0:
165        msg = "Compilation error:\n"
166        msg += py_str(out)
167        raise RuntimeError(msg)
168    link_cmd = ["lld-link"]
169    link_cmd += ["-dll", "-FORCE:MULTIPLE"]
170
171    for obj in objects:
172        if obj.endswith(".cc"):
173            (_, temp_file_name) = os.path.split(obj)
174            (shot_name, _) = os.path.splitext(temp_file_name)
175            link_cmd += [os.path.join(temp_path, shot_name + ".obj")]
176        if obj.endswith(".o"):
177            link_cmd += [obj]
178
179    link_cmd += ["-EXPORT:__tvm_main__"]
180    link_cmd += [temp_path + "dllmain.obj"]
181    link_cmd += ["-out:" + output]
182
183    try:
184        proc = subprocess.Popen(
185            link_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
186        (out, _) = proc.communicate()
187    except FileNotFoundError:
188        raise RuntimeError("Can not find the LLVM linker for Windows (lld-link.exe)."
189                           "Make sure it's installed"
190                           " and the installation directory is in the %PATH% environment "
191                           "variable. Prebuilt binaries can be found at: https://llvm.org/"
192                           "For building the linker on your own see: https://lld.llvm.org/#build")
193    if proc.returncode != 0:
194        msg = "Compilation error:\n"
195        msg += py_str(out)
196
197        raise RuntimeError(msg)
198