1#!/usr/bin/env python
2#
3# Copyright 2019 the V8 project authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6"""\
7Helper script for compiling and running the Wasm C/C++ API examples.
8
9Usage: tools/run-wasm-api-tests.py outdir tempdir [filters...]
10
11"outdir" is the build output directory containing libwee8, e.g. out/x64.release
12"tempdir" is a temporary dir where this script may put its artifacts. It is
13the caller's responsibility to clean it up afterwards.
14
15By default, this script builds and runs all examples, both the respective
16C and C++ versions, both with GCC ("gcc" and "g++" binaries found in $PATH)
17and V8's bundled Clang in third_party/llvm-build/. You can use any number
18of "filters" arguments to run only a subset:
19 - "c": run C versions of examples
20 - "cc": run C++ versions of examples
21 - "gcc": compile with GCC
22 - "clang": compile with Clang
23 - "hello" etc.: run "hello" example
24"""
25
26from __future__ import print_function
27
28import os
29import shutil
30import subprocess
31import sys
32
33CFLAGS = "-DDEBUG -Wall -Werror -O0 -ggdb -fsanitize=address"
34
35CHECKOUT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
36WASM_PATH = os.path.join(CHECKOUT_PATH, "third_party", "wasm-api")
37CLANG_PATH = os.path.join(CHECKOUT_PATH, "third_party", "llvm-build",
38                          "Release+Asserts", "bin")
39
40EXAMPLES = ["hello", "callback", "trap", "reflect", "global", "table",
41            "memory", "finalize", "serialize", "threads", "hostref", "multi",
42            "start"]
43
44CLANG = {
45  "name": "Clang",
46  "c": os.path.join(CLANG_PATH, "clang"),
47  "cc": os.path.join(CLANG_PATH, "clang++"),
48  "ldflags": "-fsanitize-memory-track-origins -fsanitize-memory-use-after-dtor",
49}
50GCC = {
51  "name": "GCC",
52  "c": "gcc",
53  "cc": "g++",
54  "ldflags": "",
55}
56
57C = {
58  "name": "C",
59  "suffix": "c",
60  "cflags": "",
61}
62CXX = {
63  "name": "C++",
64  "suffix": "cc",
65  "cflags": "-std=c++11",
66}
67
68MIN_ARGS = 3  # Script, outdir, tempdir
69
70def _Call(cmd_list, silent=False):
71  cmd = " ".join(cmd_list)
72  if not silent: print("# %s" % cmd)
73  return subprocess.call(cmd, shell=True)
74
75class Runner(object):
76  def __init__(self, name, outdir, tempdir):
77    self.name = name
78    self.outdir = outdir
79    self.tempdir = tempdir
80    self.src_file_basename = os.path.join(WASM_PATH, "example", name)
81    self.dst_file_basename = os.path.join(tempdir, name)
82    self.lib_file = os.path.join(outdir, "obj", "libwee8.a")
83    if not os.path.exists(self.lib_file):
84      print("libwee8 library not found, make sure to pass the outdir as "
85            "first argument; see --help")
86      sys.exit(1)
87    src_wasm_file = self.src_file_basename + ".wasm"
88    dst_wasm_file = self.dst_file_basename + ".wasm"
89    shutil.copyfile(src_wasm_file, dst_wasm_file)
90
91  def _Error(self, step, lang, compiler, code):
92    print("Error: %s failed. To repro: tools/run-wasm-api-tests.py "
93          "%s %s %s %s %s" %
94          (step, self.outdir, self.tempdir, self.name, lang,
95           compiler["name"].lower()))
96    return code
97
98  def CompileAndRun(self, compiler, language):
99    print("==== %s %s/%s ====" %
100          (self.name, language["name"], compiler["name"]))
101    lang = language["suffix"]
102    src_file = self.src_file_basename + "." + lang
103    exe_file = self.dst_file_basename + "-" + lang
104    obj_file = exe_file  + ".o"
105    # Compile.
106    c = _Call([compiler[lang], "-c", language["cflags"], CFLAGS,
107               "-I", WASM_PATH, "-o", obj_file, src_file])
108    if c: return self._Error("compilation", lang, compiler, c)
109    # Link.
110    c = _Call([compiler["cc"], CFLAGS, compiler["ldflags"], obj_file,
111               "-o", exe_file, self.lib_file, "-ldl -pthread"])
112    if c: return self._Error("linking", lang, compiler, c)
113    # Execute.
114    exe_file = "./%s-%s" % (self.name, lang)
115    c = _Call(["cd", self.tempdir, ";", exe_file])
116    if c: return self._Error("execution", lang, compiler, c)
117    return 0
118
119def Main(args):
120  if (len(args) < MIN_ARGS or args[1] in ("-h", "--help", "help")):
121    print(__doc__)
122    return 1
123
124  outdir = sys.argv[1]
125  tempdir = sys.argv[2]
126  result = 0
127  examples = EXAMPLES
128  compilers = (GCC, CLANG)
129  languages = (C, CXX)
130  if len(args) > MIN_ARGS:
131    custom_compilers = []
132    custom_languages = []
133    custom_examples = []
134    for i in range(MIN_ARGS, len(args)):
135      arg = args[i]
136      if arg == "c" and C not in custom_languages:
137        custom_languages.append(C)
138      elif arg in ("cc", "cpp", "cxx", "c++") and CXX not in custom_languages:
139        custom_languages.append(CXX)
140      elif arg in ("gcc", "g++") and GCC not in custom_compilers:
141        custom_compilers.append(GCC)
142      elif arg in ("clang", "clang++") and CLANG not in custom_compilers:
143        custom_compilers.append(CLANG)
144      elif arg in EXAMPLES and arg not in custom_examples:
145        custom_examples.append(arg)
146      else:
147        print("Didn't understand '%s'" % arg)
148        return 1
149    if custom_compilers:
150      compilers = custom_compilers
151    if custom_languages:
152      languages = custom_languages
153    if custom_examples:
154      examples = custom_examples
155  for example in examples:
156    runner = Runner(example, outdir, tempdir)
157    for compiler in compilers:
158      for language in languages:
159        c = runner.CompileAndRun(compiler, language)
160        if c: result = c
161  if result:
162    print("\nFinished with errors.")
163  else:
164    print("\nFinished successfully.")
165  return result
166
167if __name__ == "__main__":
168  sys.exit(Main(sys.argv))
169