OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/python | |
2 # Copyright 2014 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 # pylint: disable=W0104,W0106,F0401,R0201 | |
7 | |
8 import optparse | |
9 import os.path | |
10 import sys | |
11 | |
12 import interface | |
13 | |
14 | |
15 def _ScriptDir(): | |
16 return os.path.dirname(os.path.abspath(__file__)) | |
17 | |
18 | |
19 def _GetDirAbove(dirname): | |
20 """Returns the directory "above" this file containing |dirname| (which must | |
21 also be "above" this file).""" | |
22 path = _ScriptDir() | |
23 while True: | |
24 path, tail = os.path.split(path) | |
25 assert tail | |
26 if tail == dirname: | |
27 return path | |
28 | |
29 | |
30 def _AddThirdPartyImportPath(): | |
31 sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party")) | |
Mark Seaborn
2014/09/11 19:24:01
Use '' quotes
Nick Bray
2014/09/11 20:24:07
Done. The irony being this was originally copied
| |
32 | |
33 | |
34 _AddThirdPartyImportPath() | |
35 import jinja2 | |
36 | |
37 loader = jinja2.FileSystemLoader(_ScriptDir()) | |
38 jinja_env = jinja2.Environment(loader=loader, keep_trailing_newline=True) | |
39 | |
40 | |
41 # Accumulate lines of code with varying levels of indentation. | |
42 class CodeWriter(object): | |
43 def __init__(self): | |
44 self._lines = [] | |
45 self._margin = '' | |
46 self._margin_stack = [] | |
47 | |
48 def __lshift__(self, line): | |
49 self._lines.append((self._margin + line).rstrip()) | |
50 | |
51 def PushMargin(self): | |
52 self._margin_stack.append(self._margin) | |
53 self._margin += ' ' | |
54 | |
55 def PopMargin(self): | |
56 self._margin = self._margin_stack.pop() | |
57 | |
58 def GetValue(self): | |
59 return '\n'.join(self._lines).rstrip() + '\n' | |
60 | |
61 def Indent(self): | |
62 return Indent(self) | |
63 | |
64 | |
65 # Context handler that automatically indents and dedents a CodeWriter | |
66 class Indent(object): | |
67 def __init__(self, writer): | |
68 self._writer = writer | |
69 | |
70 def __enter__(self): | |
71 self._writer.PushMargin() | |
72 | |
73 def __exit__(self, type_, value, traceback): | |
74 self._writer.PopMargin() | |
75 | |
76 | |
77 def TemplateFile(name): | |
78 return os.path.join(os.path.dirname(__file__), name) | |
79 | |
80 | |
81 # Wraps comma separated lists as needed. | |
82 def Wrap(pre, items, post): | |
83 complete = pre + ', '.join(items) + post | |
84 if len(complete) <= 80: | |
85 return [complete] | |
86 lines = [pre] | |
87 indent = ' ' | |
88 for i, item in enumerate(items): | |
89 if i < len(items) - 1: | |
90 lines.append(indent + item + ',') | |
91 else: | |
92 lines.append(indent + item + post) | |
93 return lines | |
94 | |
Mark Seaborn
2014/09/11 19:24:01
Can you consistently put 2 empty lines between top
Nick Bray
2014/09/11 20:24:08
Done.
| |
95 def GeneratorWarning(): | |
96 return ('// WARNING this file was generated by %s\n// Do not edit by hand.' % | |
97 os.path.basename(__file__)) | |
98 | |
99 # Untrusted library implementing the public Mojo API. | |
100 def GenerateLibMojo(functions, out): | |
101 template = jinja_env.get_template('libmojo.cc.tmpl') | |
102 | |
103 code = CodeWriter() | |
104 | |
105 for f in functions: | |
106 for line in Wrap('%s %s(' % (f.returnType, f.name), f.ParamList(), '){'): | |
Mark Seaborn
2014/09/11 19:24:01
Nit: add space in ') {'
Nick Bray
2014/09/11 20:24:07
Done.
| |
107 code << line | |
108 num_params = len(f.params) + 2 | |
Mark Seaborn
2014/09/11 19:24:01
Add a comment: Add 2 for the operation ID and retu
Nick Bray
2014/09/11 20:24:07
Done.
| |
109 | |
110 with code.Indent(): | |
111 code << 'uint32_t params[%d];' % num_params | |
112 return_type = f.resultParam.baseType | |
113 if return_type == 'MojoResult': | |
114 default = 'MOJO_RESULT_INVALID_ARGUMENT' | |
115 elif return_type == 'MojoTimeTicks': | |
116 default = '0' | |
117 else: | |
118 raise Exception('Unhandled return type: ' + return_type) | |
119 code << '%s %s = %s;' % (return_type, f.resultParam.name, default) | |
120 | |
121 # Message ID | |
122 code << 'params[0] = %d;' % f.uid | |
123 # Parameter pointers | |
124 cast_template = 'params[%d] = reinterpret_cast<uint32_t>(%s);' | |
125 for p in f.params: | |
126 ptr = p.name | |
127 if p.IsPassedByValue(): | |
128 ptr = '&' + ptr | |
129 code << cast_template % (p.uid + 1, ptr) | |
130 # Return value pointer | |
131 code << cast_template % (num_params - 1, '&' + f.resultParam.name) | |
132 | |
133 code << 'DoMojoCall(params, sizeof(params));' | |
134 code << 'return %s;' % f.resultParam.name | |
135 | |
136 code << '}' | |
137 code << '' | |
138 | |
139 body = code.GetValue() | |
140 text = template.render( | |
141 generator_warning=GeneratorWarning(), | |
142 body=body) | |
143 out.write(text) | |
144 | |
145 | |
146 # Parameters passed into trusted code are handled differently depending on | |
Mark Seaborn
2014/09/11 19:24:02
Can you make this more explicit by starting with:
Nick Bray
2014/09/11 20:24:08
Clarified, although these instances will likely cr
| |
147 # details of the parameter. Encapsulate these differences in a polymorphic type. | |
148 class ParamImpl(object): | |
Mark Seaborn
2014/09/11 19:24:01
For each overridden method, can you add something
Nick Bray
2014/09/11 20:24:08
Done.
| |
149 def __init__(self, param): | |
150 self.param = param | |
151 | |
152 def CopyOut(self, code): | |
153 pass | |
154 | |
155 def IsArray(self): | |
156 return False | |
157 | |
158 | |
159 class ScalarInputImpl(ParamImpl): | |
160 def DeclareVars(self, code): | |
161 code << '%s %s_value;' % (self.param.baseType, self.param.name) | |
162 | |
163 def ConvertParam(self): | |
164 p = self.param | |
165 return ('ConvertScalarInput(nap, params[%d], &%s_value)' % | |
166 (p.uid + 1, p.name)) | |
167 | |
168 def CallParam(self): | |
169 return '%s_value' % self.param.name | |
170 | |
171 | |
172 class ScalarOutputImpl(ParamImpl): | |
173 def DeclareVars(self, code): | |
174 code << '%s volatile* %s_ptr;' % (self.param.baseType, self.param.name) | |
175 code << '%s %s_value;' % (self.param.baseType, self.param.name) | |
176 | |
177 def ConvertParam(self): | |
178 p = self.param | |
179 return 'ConvertScalarOutput(nap, params[%d], &%s_ptr)' % (p.uid + 1, p.name) | |
180 | |
181 def CallParam(self): | |
182 return '&%s_value' % self.param.name | |
183 | |
184 def CopyOut(self, code): | |
185 name = self.param.name | |
186 code << '*%s_ptr = %s_value;' % (name, name) | |
187 | |
188 | |
189 class ScalarInOutImpl(ParamImpl): | |
190 def DeclareVars(self, code): | |
191 code << '%s volatile* %s_ptr;' % (self.param.baseType, self.param.name) | |
192 code << '%s %s_value;' % (self.param.baseType, self.param.name) | |
193 | |
194 def ConvertParam(self): | |
195 p = self.param | |
196 return ('ConvertScalarInOut(nap, params[%d], %s, &%s_value, &%s_ptr)' % | |
197 (p.uid + 1, CBool(p.isOptional), p.name, p.name)) | |
198 | |
199 def CallParam(self): | |
200 name = self.param.name | |
201 expr = '&%s_value' % name | |
202 if self.param.isOptional: | |
203 expr = '%s_ptr ? %s : NULL' % (name, expr) | |
204 return expr | |
205 | |
206 def CopyOut(self, code): | |
207 name = self.param.name | |
208 if self.param.isOptional: | |
209 code << 'if (%s_ptr != NULL) {' % (name) | |
210 with code.Indent(): | |
211 code << '*%s_ptr = %s_value;' % (name, name) | |
212 code << '}' | |
213 else: | |
214 code << '*%s_ptr = %s_value;' % (name, name) | |
215 | |
216 | |
217 class ArrayImpl(ParamImpl): | |
218 def DeclareVars(self, code): | |
219 code << '%s %s;' % (self.param.paramType, self.param.name) | |
220 | |
221 def ConvertParam(self): | |
222 p = self.param | |
223 if p.baseType == 'void': | |
224 return ('ConvertBytes(nap, params[%d], %s, %s, &%s)' % | |
Mark Seaborn
2014/09/11 19:24:01
Why have separate ConvertBytes/ConvertArray functi
Nick Bray
2014/09/11 20:24:08
Done. I am not convinced adding an extra paramete
| |
225 (p.uid + 1, p.size + '_value', CBool(p.isOptional), p.name)) | |
226 else: | |
227 return ('ConvertArray(nap, params[%d], %s, %s, &%s)' % | |
228 (p.uid + 1, p.size + '_value', CBool(p.isOptional), p.name)) | |
229 | |
230 def CallParam(self): | |
231 return self.param.name | |
232 | |
233 def IsArray(self): | |
234 return True | |
235 | |
236 | |
237 class StructInputImpl(ParamImpl): | |
238 def DeclareVars(self, code): | |
239 code << '%s %s;' % (self.param.paramType, self.param.name) | |
240 | |
241 def ConvertParam(self): | |
242 p = self.param | |
243 return ('ConvertStruct(nap, params[%d], %s, &%s)' % | |
244 (p.uid + 1, CBool(p.isOptional), p.name)) | |
245 | |
246 def CallParam(self): | |
247 return self.param.name | |
248 | |
249 | |
250 def ImplForParam(p): | |
251 if p.IsScalar(): | |
252 if p.isOutput: | |
253 if p.isInput: | |
254 return ScalarInOutImpl(p) | |
255 else: | |
256 return ScalarOutputImpl(p) | |
257 else: | |
258 return ScalarInputImpl(p) | |
259 elif p.isArray: | |
260 return ArrayImpl(p) | |
261 elif p.isStruct: | |
262 return StructInputImpl(p) | |
263 else: | |
264 assert False, p | |
265 | |
266 | |
267 def CBool(value): | |
268 return 'true' if value else 'false' | |
269 | |
270 # A trusted wrapper that validates the arguments passed from untrusted code | |
271 # before passing them to the underlying public Mojo API. | |
272 def GenerateMojoSyscall(functions, out): | |
273 template = jinja_env.get_template('mojo_syscall.cc.tmpl') | |
274 | |
275 code = CodeWriter() | |
276 code.PushMargin() | |
277 | |
278 for f in functions: | |
279 impls = [ImplForParam(p) for p in f.params] | |
280 impls.append(ImplForParam(f.resultParam)) | |
281 | |
282 code << 'case %d:' % f.uid | |
283 | |
284 code.PushMargin() | |
285 | |
286 code << '{' | |
287 | |
288 with code.Indent(): | |
289 num_params = len(f.params) + 2 | |
290 code << 'if (num_params != %d) {' % num_params | |
291 with code.Indent(): | |
292 code << 'return -1;' | |
293 code << '}' | |
294 | |
295 # Declare temporaries. | |
296 for impl in impls: | |
297 impl.DeclareVars(code) | |
298 | |
299 def ConvertParam(code, impl): | |
300 code << 'if (!%s) {' % impl.ConvertParam() | |
301 with code.Indent(): | |
302 code << 'return -1;' | |
303 code << '}' | |
304 | |
305 code << '{' | |
306 with code.Indent(): | |
307 code << 'ScopedCopyLock copy_lock(nap);' | |
308 # Convert and validate pointers in two passes. | |
309 # Arrays cannot be validated util the size parameter has been converted. | |
Mark Seaborn
2014/09/11 19:24:01
"until"
Nick Bray
2014/09/11 20:24:07
Done.
| |
310 for impl in impls: | |
311 if not impl.IsArray(): | |
312 ConvertParam(code, impl) | |
313 for impl in impls: | |
314 if impl.IsArray(): | |
315 ConvertParam(code, impl) | |
316 code << '}' | |
317 code << '' | |
318 | |
319 # Call | |
320 getParams = [impl.CallParam() for impl in impls[:-1]] | |
321 code << 'result_value = %s(%s);' % (f.name, ', '.join(getParams)) | |
322 code << '' | |
323 | |
324 # Write outputs | |
325 code << '{' | |
326 with code.Indent(): | |
327 code << 'ScopedCopyLock copy_lock(nap);' | |
328 for impl in impls: | |
329 impl.CopyOut(code) | |
330 code << '}' | |
331 code << '' | |
332 | |
333 code << 'return 0;' | |
334 code << '}' | |
335 | |
336 code.PopMargin() | |
337 | |
338 body = code.GetValue() | |
339 text = template.render( | |
340 generator_warning=GeneratorWarning(), | |
341 body=body) | |
342 out.write(text) | |
343 | |
344 def OutFile(dir_path, name): | |
345 if not os.path.exists(dir_path): | |
346 os.makedirs(dir_path) | |
347 return open(os.path.join(dir_path, name), 'w') | |
348 | |
349 def main(args): | |
350 usage = 'usage: %prog [options]' | |
351 parser = optparse.OptionParser(usage=usage) | |
352 parser.add_option( | |
353 '-d', | |
354 dest='out_dir', | |
355 metavar='DIR', | |
356 help='output generated code into directory DIR') | |
357 options, args = parser.parse_args(args=args) | |
358 if not options.out_dir: | |
359 parser.error('-d is required') | |
360 if args: | |
361 parser.error('unexpected positional arguments: %s' % ' '.join(args)) | |
362 | |
363 mojo = interface.MakeInterface() | |
364 | |
365 out = OutFile(options.out_dir, 'libmojo.cc') | |
366 GenerateLibMojo(mojo.functions, out) | |
367 | |
368 out = OutFile(options.out_dir, 'mojo_syscall.cc') | |
369 GenerateMojoSyscall(mojo.functions, out) | |
370 | |
371 | |
372 if __name__ == '__main__': | |
373 main(sys.argv[1:]) | |
OLD | NEW |