Skip to content

Commit 3ddfad9

Browse files
Add regalloc_trace_worker
This patch adds the worker for the new trace-based regalloc training mode. Reviewers: mtrofin Reviewed By: mtrofin Pull Request: #415
1 parent 28a0353 commit 3ddfad9

File tree

3 files changed

+344
-0
lines changed

3 files changed

+344
-0
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# coding=utf-8
2+
# Copyright 2020 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# coding=utf-8
2+
# Copyright 2020 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
"""Worker for regalloc using trace-based cost modeling.
16+
17+
This worker is designed specifically for a trace based cost modelling
18+
methodology. It compiles an entire corpus in parallel with a thread pool, and
19+
then passes all those modules to basic_block_trace_model along with traces and
20+
other relevant data to produce an overall cost for the model being evaluated.
21+
"""
22+
23+
from typing import Optional, Collection
24+
import os
25+
import pathlib
26+
import subprocess
27+
import json
28+
import concurrent.futures
29+
import tempfile
30+
31+
import gin
32+
33+
from compiler_opt.rl import corpus
34+
from compiler_opt.distributed import worker
35+
from compiler_opt.rl import policy_saver
36+
37+
38+
@gin.configurable
39+
class RegallocTraceWorker(worker.Worker):
40+
"""A worker that produces rewards for a given regalloc policy.
41+
42+
RegallocTraceWorker exposes a compile_corpus_and_evaluate function, which
43+
compiles a set of modules in parallel locally, evaluates them with
44+
basic_block_trace_model, and then returns the total cost of the evaluated
45+
segments.
46+
"""
47+
48+
def __init__(self, clang_path: str, basic_block_trace_model_path: str,
49+
thread_count: int, corpus_path: str):
50+
"""Initializes the RegallocTraceWorker class.
51+
52+
Args:
53+
clang_path: The path to the clang binary to use for compiling the corpus.
54+
basic_block_trace_model_path: The path to the basic_block_trace_model
55+
binary to use for trace-based modelling. basic_block_trace_model takes
56+
in a set of modules, a trace, and auxiliary information for
57+
interpreting the trace, simulates the trace against the code in the
58+
passed-in modules, returning estimated cycle counts.
59+
thread_count: The number of threads to use for concurrent compilation
60+
and modelling.
61+
corpus_path: The path to the corpus that modules will be compiled from.
62+
"""
63+
self._clang_path = clang_path
64+
self._basic_block_trace_model_path = basic_block_trace_model_path
65+
self._thread_count = thread_count
66+
self._corpus_path = corpus_path
67+
68+
def _compile_module(self, module_to_compile: corpus.ModuleSpec,
69+
output_directory: str, tflite_policy_path: Optional[str]):
70+
command_vector = [self._clang_path]
71+
context = corpus.Corpus.ReplaceContext(
72+
os.path.join(self._corpus_path, module_to_compile.name) + ".bc",
73+
# We add the additional ThinLTO index unconditionallyas if we are not
74+
# using ThinLTO, we will just never end up replacing anything.
75+
os.path.join(self._corpus_path, module_to_compile.name) + ".thinlto.bc")
76+
command_vector.extend([
77+
option.format(context=context)
78+
for option in module_to_compile.command_line
79+
])
80+
81+
if tflite_policy_path is not None:
82+
command_vector.extend([
83+
"-mllvm", "-regalloc-enable-advisor=development", "-mllvm",
84+
f"-regalloc-model={tflite_policy_path}"
85+
])
86+
else:
87+
# Force the default advisor if we aren't explicitly using a new policy
88+
# to prevent enabling the release advisor if it was specified in the
89+
# corpus.
90+
command_vector.extend(["-mllvm", "-regalloc-enable-advisor=default"])
91+
92+
module_output_path = os.path.join(output_directory,
93+
module_to_compile.name + ".bc.o")
94+
pathlib.Path(os.path.dirname(module_output_path)).mkdir(
95+
parents=True, exist_ok=True)
96+
command_vector.extend(["-o", module_output_path])
97+
98+
subprocess.run(
99+
command_vector,
100+
check=True,
101+
stdout=subprocess.PIPE,
102+
stderr=subprocess.PIPE)
103+
104+
def _build_corpus(self, modules: Collection[corpus.ModuleSpec],
105+
output_directory: str,
106+
tflite_policy: Optional[policy_saver.Policy]):
107+
with tempfile.TemporaryDirectory() as tflite_policy_dir:
108+
if tflite_policy:
109+
tflite_policy.to_filesystem(tflite_policy_dir)
110+
else:
111+
tflite_policy_dir = None
112+
113+
compile_futures = []
114+
with concurrent.futures.ThreadPoolExecutor(
115+
max_workers=self._thread_count) as thread_pool:
116+
for module in modules:
117+
compile_futures.append(
118+
thread_pool.submit(self._compile_module, module, output_directory,
119+
tflite_policy_dir))
120+
121+
for future in compile_futures:
122+
if future.exception() is not None:
123+
raise future.exception()
124+
125+
# Write out a corpus description. basic_block_trace_model uses a corpus
126+
# description JSON to know which object files to load, so we need to emit
127+
# one before performing evaluation.
128+
corpus_description_path = os.path.join(output_directory,
129+
"corpus_description.json")
130+
corpus_description = {
131+
"modules": [module_spec.name for module_spec in modules]
132+
}
133+
134+
with open(
135+
corpus_description_path, "w",
136+
encoding="utf-8") as corpus_description_file:
137+
json.dump(corpus_description, corpus_description_file)
138+
139+
def _evaluate_corpus(self, module_directory: str, function_index_path: str,
140+
bb_trace_path: str):
141+
corpus_description_path = os.path.join(module_directory,
142+
"corpus_description.json")
143+
144+
command_vector = [
145+
self._basic_block_trace_model_path,
146+
f"--corpus_path={corpus_description_path}",
147+
f"--function_index_path={function_index_path}",
148+
f"--thread_count={self._thread_count}",
149+
f"--bb_trace_path={bb_trace_path}", "--model_type=mca"
150+
]
151+
152+
output = subprocess.run(
153+
command_vector,
154+
stdout=subprocess.PIPE,
155+
stderr=subprocess.PIPE,
156+
check=True)
157+
158+
segment_costs = []
159+
for line in output.stdout.decode("utf-8").split("\n"):
160+
try:
161+
value = float(line)
162+
segment_costs.append(value)
163+
except ValueError:
164+
continue
165+
166+
if len(segment_costs) < 1:
167+
raise ValueError("Did not find any valid segment costs.")
168+
169+
return segment_costs
170+
171+
def compile_corpus_and_evaluate(
172+
self, modules: Collection[corpus.ModuleSpec], function_index_path: str,
173+
bb_trace_path: str,
174+
tflite_policy: Optional[policy_saver.Policy]) -> float:
175+
with tempfile.TemporaryDirectory() as compilation_dir:
176+
self._build_corpus(modules, compilation_dir, tflite_policy)
177+
178+
segment_costs = self._evaluate_corpus(compilation_dir,
179+
function_index_path, bb_trace_path)
180+
return sum(segment_costs)
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# coding=utf-8
2+
# Copyright 2020 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
"""Test for RegallocTraceWorker."""
16+
17+
from typing import List
18+
import os
19+
import json
20+
import stat
21+
import textwrap
22+
23+
from absl.testing import absltest
24+
25+
from compiler_opt.es.regalloc_trace import regalloc_trace_worker
26+
from compiler_opt.rl import corpus
27+
from compiler_opt.testing import model_test_utils
28+
from compiler_opt.rl import policy_saver
29+
30+
31+
def _setup_corpus(corpus_dir: str) -> List[corpus.ModuleSpec]:
32+
modules = [
33+
corpus.ModuleSpec("module_a", 1, ("-fmodule-a",), True),
34+
corpus.ModuleSpec("module_b", 1, ("-fmodule-b",), True)
35+
]
36+
37+
corpus_description = {
38+
"has_thinlto": True,
39+
"modules": [os.path.join(corpus_dir, module.name) for module in modules]
40+
}
41+
42+
with open(
43+
os.path.join(corpus_dir, "corpus_description.json"),
44+
"w",
45+
encoding="utf-8") as corpus_description_handle:
46+
json.dump(corpus_description, corpus_description_handle)
47+
48+
return modules
49+
50+
51+
def _create_test_binary(binary_path: str, output_path: str):
52+
test_binary = textwrap.dedent(f"""\
53+
#!/bin/bash
54+
echo "$@" >> {output_path}
55+
echo 1
56+
echo 1
57+
""")
58+
59+
with open(binary_path, "w", encoding="utf-8") as binary_handle:
60+
binary_handle.write(test_binary)
61+
binary_stat = os.stat(binary_path)
62+
os.chmod(binary_path, binary_stat.st_mode | stat.S_IEXEC)
63+
64+
65+
class RegallocTraceWorkerTest(absltest.TestCase):
66+
67+
def test_build_corpus_and_evaluate(self):
68+
corpus_dir = self.create_tempdir("corpus")
69+
corpus_modules = _setup_corpus(corpus_dir)
70+
fake_clang_binary = self.create_tempfile("fake_clang")
71+
fake_clang_invocations = self.create_tempfile("fake_clang_invocations")
72+
_create_test_binary(fake_clang_binary.full_path,
73+
fake_clang_invocations.full_path)
74+
fake_bb_trace_model_binary = self.create_tempfile(
75+
"fake_basic_block_trace_model")
76+
fake_bb_trace_model_invocations = self.create_tempfile(
77+
"fake_basic_block_trace_model_invocations")
78+
_create_test_binary(fake_bb_trace_model_binary.full_path,
79+
fake_bb_trace_model_invocations.full_path)
80+
81+
worker = regalloc_trace_worker.RegallocTraceWorker(
82+
fake_clang_binary.full_path, fake_bb_trace_model_binary.full_path, 1,
83+
corpus_dir.full_path)
84+
total_cost = worker.compile_corpus_and_evaluate(corpus_modules,
85+
"function_index_path.pb",
86+
"bb_trace_path.pb", None)
87+
self.assertEqual(total_cost, 2)
88+
89+
# Check that we are compiling the modules with the appropriate flags and
90+
# the default regalloc advisor given we did not pass in any TFLite policy.
91+
clang_command_lines = fake_clang_invocations.read_text().split("\n")
92+
clang_command_lines.remove("")
93+
self.assertLen(clang_command_lines, 2)
94+
self.assertTrue("-fmodule-a" in clang_command_lines[0])
95+
self.assertTrue(
96+
"-regalloc-enable-advisor=default" in clang_command_lines[0])
97+
self.assertTrue("-fmodule-b" in clang_command_lines[1])
98+
self.assertTrue(
99+
"-regalloc-enable-advisor=default" in clang_command_lines[1])
100+
101+
# Check that we pass the expected flags to basic_block_trace_model.
102+
bb_trace_model_command_line = fake_bb_trace_model_invocations.read_text(
103+
).split("\n")[0].split()
104+
self.assertLen(bb_trace_model_command_line, 5)
105+
self.assertTrue("--corpus_path" in bb_trace_model_command_line[0])
106+
self.assertEqual("--function_index_path=function_index_path.pb",
107+
bb_trace_model_command_line[1])
108+
self.assertEqual("--thread_count=1", bb_trace_model_command_line[2])
109+
self.assertEqual("--bb_trace_path=bb_trace_path.pb",
110+
bb_trace_model_command_line[3])
111+
self.assertEqual("--model_type=mca", bb_trace_model_command_line[4])
112+
113+
def test_compile_corpus_and_evaluate_with_tflite(self):
114+
corpus_dir = self.create_tempdir("corpus")
115+
corpus_modules = _setup_corpus(corpus_dir)
116+
fake_clang_binary = self.create_tempfile("fake_clang")
117+
fake_clang_invocations = self.create_tempfile("fake_clang_invocations")
118+
_create_test_binary(fake_clang_binary.full_path,
119+
fake_clang_invocations.full_path)
120+
fake_bb_trace_model_binary = self.create_tempfile(
121+
"fake_basic_block_trace_model")
122+
fake_bb_trace_model_invocations = self.create_tempfile(
123+
"fake_basic_block_trace_model_invocations")
124+
_create_test_binary(fake_bb_trace_model_binary.full_path,
125+
fake_bb_trace_model_invocations.full_path)
126+
127+
saved_model_dir = self.create_tempdir("saved_model")
128+
tflite_dir = self.create_tempdir("converted_model")
129+
model_test_utils.gen_test_model(saved_model_dir.full_path)
130+
policy_saver.convert_mlgo_model(saved_model_dir.full_path,
131+
tflite_dir.full_path)
132+
serialized_policy = policy_saver.Policy.from_filesystem(
133+
tflite_dir.full_path)
134+
135+
worker = regalloc_trace_worker.RegallocTraceWorker(
136+
fake_clang_binary.full_path, fake_bb_trace_model_binary.full_path, 1,
137+
corpus_dir.full_path)
138+
worker.compile_corpus_and_evaluate(corpus_modules, "function_index_path.pb",
139+
"bb_trace_path.pb", serialized_policy)
140+
141+
# Assert that we pass the TFLite model to the clang invocations.
142+
clang_command_lines = fake_clang_invocations.read_text().split("\n")
143+
clang_command_lines.remove("")
144+
self.assertLen(clang_command_lines, 2)
145+
self.assertTrue(
146+
"-regalloc-enable-advisor=development" in clang_command_lines[0])
147+
self.assertTrue("-regalloc-model=" in clang_command_lines[0])
148+
self.assertTrue(
149+
"-regalloc-enable-advisor=development" in clang_command_lines[1])
150+
self.assertTrue("-regalloc-model=" in clang_command_lines[1])

0 commit comments

Comments
 (0)