summaryrefslogtreecommitdiff
path: root/scripts/dietcc
blob: 540d1684d4ce3a964ecc4838d0851a29941d4e24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#!/bin/python3
import subprocess
import sys
import re
import tempfile
import os
import shlex

CC = "gcc"

if sys.argv[1:] == ["--version"]:
  print("DIETC")
  sys.exit(0)

# Have to do this because ./configure seems to do some nasty stuff
# https://lists.gnu.org/archive/html/bug-make/2022-11/msg00041.html
if "-E" in sys.argv[1:]:
  os.system(f"{CC} {' '.join(map(shlex.quote, sys.argv[1:]))}")
  sys.exit(0)

# https://stackoverflow.com/questions/595305
dietc_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))

def check_result(spr):
  if not spr.returncode: return spr.stdout
  print("Failed to run gcc wrapper:", ' '.join(sys.argv), file=sys.stderr)
  if spr.stderr:
    print(spr.stderr.decode("utf-8"), file=sys.stderr)
  assert False

def preprocess_pass(c, meta):
  with tempfile.TemporaryDirectory() as tmpdir:
    t_file = open(f"{tmpdir}/file.c", "wb")
    t_file.write(f"#include \"{dietc_dir}/scripts/stdincludes/dietc_defines.h\"\n".encode("utf-8"))
    t_file.write(c)
    t_file.flush()
    dirname = os.path.dirname(os.path.realpath(meta["c_path"]))
    return check_result(
      subprocess.run(f"{CC} -I{dirname} -E {meta['argstr']} {t_file.name}",
                     shell=True, capture_output=True))

def strip_after_preprocess_pass(c, meta):
  c = re.sub(rb"\[\[[^\]]*\]\]", b"", c)
  c = re.sub(rb"__asm__", b"", c)
  return c

def dietc_pass(c, meta):
  with tempfile.TemporaryDirectory() as tmpdir:
    t_file = open(f"{tmpdir}/file.c", "wb")
    t_file.write(c)
    t_file.flush()
    dirname = os.path.dirname(os.path.realpath(meta["c_path"]))
    return check_result(
      subprocess.run(f"timeout 5s {dietc_dir}/dietc {t_file.name}",
                     shell=True, capture_output=True))

def final_cleanup_pass(dietc, meta):
  # (1) treat builtins as builtins
  patterns = {
      rb"extern Type_[0-9]* __builtin_[^ ]* ;\n": b"",
      rb"extern Type_[0-9]* __(div|mul)[ds]c3 ;\n": b"",
      rb"extern Type_[0-9]* __btowc_alias ;\n": b"",
  }
  for pattern, replace in patterns.items():
    dietc = re.sub(pattern, b"", dietc)

  # (2) builtins must be called directly
  # NOTE: if passes mess this up, e.g., by doing optimizations, we could be in
  # trouble here
  pattern = rb"\t(t[0-9]*) = (__builtin_[^ ]*|alloca) ;\n"
  match_ = re.search(pattern, dietc)
  while match_ is not None:
    temp = match_.groups()[0]
    builtin = match_.groups()[1]
    # (2a) delete the match
    dietc = dietc.replace(b"\t" + temp + b" = " + builtin + b" ;\n", b"")
    # (2b) delete the declaration of the builtin
    dietc = re.sub(b"\tType_[0-9]* " + temp + b" ;\n", b"", dietc)
    # (2c) replace uses of the temp with the builtin
    dietc = dietc.replace(b" " + temp + b" ", b" " + builtin + b" ")
    dietc = dietc.replace(b"\t" + temp + b" ", b"\t" + builtin + b" ")

    match_ = re.search(pattern, dietc)

  # (3) __builtin_va_arg_pack* must be called *super* directly, and must be in
  # an always-inline method
  # NOTE: if passes mess this up, e.g., by doing optimizations, we could be in
  # trouble here
  pattern = rb"\t(t[0-9]*) = (__builtin_va_arg_pack[^ ]*) \( \) ;\n"
  match_ = re.search(pattern, dietc)
  any_arg_pack = (match_ is not None)
  while match_ is not None:
    temp = match_.groups()[0]
    builtin = match_.groups()[1]
    # (2a) delete the match
    dietc = dietc.replace(b"\t" + temp + b" = " + builtin + b" ( ) ;\n", b"")
    # (2b) delete the declaration of the result
    dietc = re.sub(b"\tType_[0-9]* " + temp + b" ;\n", b"", dietc)
    # (2c) replace uses of the temp with the builtin
    dietc = dietc.replace(b" " + temp + b" ", b" " + builtin + b" ( ) ")

    match_ = re.search(pattern, dietc)

  # (3b) any functions using __builtin_va_arg_pack* must be inlined
  if any_arg_pack or b"\t__builtin_va_start " in dietc:
    lines = dietc.split(b"\n")
    inline_is = set()
    last_decl_i = 0
    for i, line in enumerate(lines):
      if not line.startswith(b"\t"):
        last_decl_i = i
      elif b"__builtin_va_arg_pack" in line:
        inline_is.add(last_decl_i)
      elif b"\t__builtin_va_start " in line:
        # second argument of __builtin_va_start must be last named argument to
        # function
        fn, _po, arg1, _c, arg2, _pc, _sc = line.split()
        # {, ), ..., <,>, [arg]
        real_arg2 = lines[last_decl_i].split()[-5]
        lines[i] = b"\t" + b" ".join([fn, _po, arg1, _c, real_arg2, _pc, _sc])
    for i in sorted(inline_is):
      if "static" in lines[i]:
        lines[i] = b"inline __attribute__ ((__gnu_inline__)) " + lines[i]
      else:
        lines[i] = b"extern inline __attribute__ ((__gnu_inline__)) " + lines[i]
    dietc = b"\n".join(lines)

  return dietc

def make_external_pass(command):
  def pass_(dietc, meta):
    with tempfile.TemporaryDirectory() as tmpdir:
      t_file = open(f"{tmpdir}/file.c", "wb")
      t_file.write(dietc)
      t_file.flush()
      return check_result(subprocess.run(f"{command} {t_file.name}",
                          shell=True, capture_output=True))
  return pass_

PASSES = [preprocess_pass,
          strip_after_preprocess_pass,
          dietc_pass,
          final_cleanup_pass]

def main():
  args = list(map(shlex.quote, sys.argv[1:]))
  args.insert(0, f"-I{dietc_dir}/scripts/stdincludes")
  # first, parse out any DietC passes
  while "--dietc-pass" in args:
    i = args.index("--dietc-pass")
    args.pop(i)
    PASSES.insert(-1, make_external_pass(args.pop(i)))

  # then process all of the C files
  out_dir = tempfile.TemporaryDirectory()
  c_files = [arg for arg in args if arg.endswith(".c")]
  diet_files = []
  for i, c_file in enumerate(c_files):
    subargs = [arg for arg in args if not arg.endswith(".c")]
    if "-o" in subargs:
      i_ = subargs.index("-o")
      subargs.pop(i_)
      subargs.pop(i_)
    argstr = " ".join(subargs)

    last = open(c_file, "rb").read()
    for dietpass in PASSES:
      last = dietpass(last, {"c_path": c_file, "argstr": argstr})

    diet_files.append(f"{out_dir.name}/file{i}.c")
    with open(diet_files[-1], "wb") as f:
      f.write(last)

  # then reconstruct the arguments, using the processed C files
  renaming = dict({c: diet for c, diet in zip(c_files, diet_files)})
  args = [renaming.get(arg, arg) for arg in args]
  if c_files and "-o" not in args:
    if "-c" in args:
      args += ["-o", c_files[0].replace(".c", ".o")]
  gcc = subprocess.run(f"{CC} {' '.join(args)} -Wno-int-to-pointer-cast -Wno-builtin-declaration-mismatch", shell=True)
  check_result(gcc)

main()
generated by cgit on debian on lair
contact matthew@masot.net with questions or feedback