blob: d72e9e24bdd032020a5a6c9d0d0d0ed6440b51e7 [file] [log] [blame]
Rich Lanea06d0c32013-03-25 08:52:03 -07001#!/usr/bin/python
2# Copyright 2013, Big Switch Networks, Inc.
3#
4# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
5# the following special exception:
6#
7# LOXI Exception
8#
9# As a special exception to the terms of the EPL, you may distribute libraries
10# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
11# that copyright and licensing notices generated by LoxiGen are not altered or removed
12# from the LoxiGen Libraries and the notice provided below is (i) included in
13# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
14# documentation for the LoxiGen Libraries, if distributed in binary form.
15#
16# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
17#
18# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
19# a copy of the EPL at:
20#
21# http://www.eclipse.org/legal/epl-v10.html
22#
23# Unless required by applicable law or agreed to in writing, software
24# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
25# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
26# EPL for the specific language governing permissions and limitations
27# under the EPL.
28
29"""
30@brief
31Process openflow header files to create language specific LOXI interfaces
32
33First cut at simple python script for processing input files
34
35Internal notes
36
37An input file for each supported OpenFlow version is passed in
38on the command line.
39
40Expected input file format:
41
42These will probably be collapsed into a python dict or something
43
44The first line has the ofC version identifier and nothing else
45The second line has the openflow wire protocol value and nothing else
46
47The main content is struct elements for each OF recognized class.
48These are taken from current versions of openflow.h but are modified
49a bit. See Overview for more information.
50
51Class canonical form: A list of entries, each of which is a
52pair "type, name;". The exception is when type is the keyword
53'list' in which the syntax is "list(type) name;".
54
55From this, internal representations are generated: For each
56version, a dict indexed by class name. One element (members) is
57an array giving the member name and type. From this, wire offsets
58can be calculated.
59
60
61@fixme Clean up the lang module architecture. It should provide a
62list of files that it wants to generate and maps to the filenames,
63subdirectory names and generation functions. It should also be
64defined as a class, probably with the constructor taking the
65language target.
66
67@fixme Clean up global data structures such as versions and of_g
68structures. They should probably be a class or classes as well.
69
70"""
71
72import sys
73
74import re
75import string
76import os
77import glob
78import copy
79import of_g
80import loxi_front_end.oxm as oxm
81import loxi_front_end.type_maps as type_maps
82import loxi_utils.loxi_utils as loxi_utils
83import loxi_front_end.of_h_utils as of_h_utils
84import loxi_front_end.c_parse_utils as c_parse_utils
85import loxi_front_end.identifiers as identifiers
86import pyparsing
87import loxi_front_end.parser as parser
88
89from generic_utils import *
90
91root_dir = os.path.dirname(os.path.realpath(__file__))
92
93# TODO: Put these in a class so they get documented
94
95## Dict indexed by version giving all info related to version
96#
97# This is local; after processing, the information is stored in
98# of_g variables.
99versions = {}
100
101def config_sanity_check():
102 """
103 Check the configuration for basic consistency
104
105 @fixme Needs update for generic language support
106 """
107
108 rv = True
109 # For now, only "error" supported for get returns
110 if config_check("copy_semantics") != "read":
111 debug("Only 'read' is supported for copy_semantics");
112 rv = False
113 if config_check("get_returns") != "error":
114 debug("Only 'error' is supported for get-accessor return types\m");
115 rv = False
116 if not config_check("use_fn_ptrs") and not config_check("gen_unified_fns"):
117 debug("Must have gen_fn_ptrs and/or gen_unified_fns set in config")
118 rv = False
119 if config_check("use_obj_id"):
120 debug("use_obj_id is set but not yet supported (change \
121config_sanity_check if it is)")
122 rv = False
123 if config_check("gen_unified_macros") and config_check("gen_unified_fns") \
124 and config_check("gen_unified_macro_lower"):
125 debug("Conflict: Cannot generate unified functions and lower case \
126unified macros")
127 rv = False
128
129 return rv
130
131def add_class(wire_version, cls, members):
132 """
133 Process a class for the given version and update the unified
134 list of classes as needed.
135
136 @param wire_version The wire version for this class defn
137 @param cls The name of the class being added
138 @param members The list of members with offsets calculated
139 """
140 memid = 0
141
142 sig = loxi_utils.class_signature(members)
143 if cls in of_g.unified:
144 uc = of_g.unified[cls]
145 if wire_version in uc:
146 debug("Error adding %s to unified. Wire ver %d exists" %
147 (cls, wire_version))
148 sys.exit(1)
149 uc[wire_version] = {}
150 # Check for a matching signature
151 for wver in uc:
152 if type(wver) != type(0): continue
153 if wver == wire_version: continue
154 if not "use_version" in uc[wver]:
155 if sig == loxi_utils.class_signature(uc[wver]["members"]):
156 log("Matched %s, ver %d to ver %d" %
157 (cls, wire_version, wver))
158 # have a match with existing version
159 uc[wire_version]["use_version"] = wver
160 # What else to do?
161 return
162 else: # Haven't seen this entry before
163 log("Adding %s to unified list, ver %d" % (cls, wire_version))
164 of_g.unified[cls] = dict(union={})
165 uc = of_g.unified[cls]
166
167 # At this point, need to add members for this version
168 uc[wire_version] = dict(members = members)
169
170 # Per member processing:
171 # Add to union list (I'm sure there's a better way)
172 # Check if it's a list
173 union = uc["union"]
174 if not cls in of_g.ordered_members:
175 of_g.ordered_members[cls] = []
176 for member in members:
177 m_name = member["name"]
178 m_type = member["m_type"]
179 if m_name.find("pad") == 0:
180 continue
181 if m_name in union:
182 if not m_type == union[m_name]["m_type"]:
183 debug("ERROR: CLASS: %s. VERSION %d. MEMBER: %s. TYPE: %s" %
184 (cls, wire_version, m_name, m_type))
185 debug(" Type conflict adding member to unified set.")
186 debug(" Current union[%s]:" % m_name)
187 debug(union[m_name])
188 sys.exit(1)
189 else:
190 union[m_name] = dict(m_type=m_type, memid=memid)
191 memid += 1
192 if not m_name in of_g.ordered_members[cls]:
193 of_g.ordered_members[cls].append(m_name)
194
195def update_offset(cls, wire_version, name, offset, m_type):
196 """
197 Update (and return) the offset based on type.
198 @param cls The parent class
199 @param wire_version The wire version being processed
200 @param name The name of the data member
201 @param offset The current offset
202 @param m_type The type declaration being processed
203 @returns A pair (next_offset, len_update) next_offset is the new offset
204 of the next object or -1 if this is a var-length object. len_update
205 is the increment that should be added to the length. Note that (for
206 of_match_v3) it is variable length, but it adds 8 bytes to the fixed
207 length of the object
208 If offset is already -1, do not update
209 Otherwise map to base type and count and update (if possible)
210 """
211 if offset < 0: # Don't update offset once set to -1
212 return offset, 0
213
214 count, base_type = c_parse_utils.type_dec_to_count_base(m_type)
215
216 len_update = 0
217 if base_type in of_g.of_mixed_types:
218 base_type = of_g.of_mixed_types[base_type][wire_version]
219
220 base_class = base_type[:-2]
221 if (base_class, wire_version) in of_g.is_fixed_length:
222 bytes = of_g.base_length[(base_class, wire_version)]
223 else:
224 if base_type == "of_match_v3_t":
225 # This is a special case: it has non-zero min length
226 # but is variable length
227 bytes = -1
228 len_update = 8
229 elif base_type in of_g.of_base_types:
230 bytes = of_g.of_base_types[base_type]["bytes"]
231 else:
232 print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
233 log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
234 bytes = -1
235
236 # If bytes
237 if bytes > 0:
238 len_update = count * bytes
239
240 if bytes == -1:
241 return -1, len_update
242
243 return offset + (count * bytes), len_update
244
245def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
246 """
247 Generate the offsets for fixed offset class members
248 Also calculate the class_sizes when possible.
249
250 @param classes The classes to process
251 @param wire_version The wire version for this set of classes
252
253 Updates global variables
254 """
255
256 lists = set()
257
258 # Generate offsets
259 for cls in ordered_classes:
260 fixed_offset = 0 # The last "good" offset seen
261 offset = 0
262 last_offset = 0
263 last_name = "-"
264 for member in classes[cls]:
265 m_type = member["m_type"]
266 name = member["name"]
267 if last_offset == -1:
268 if name == "pad":
269 log("Skipping pad for special offset for %s" % cls)
270 else:
271 log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
272 (name, last_name, cls, wire_version))
273 if (((cls, name) in of_g.special_offsets) and
274 (of_g.special_offsets[(cls, name)] != last_name)):
275 debug("ERROR: special offset prev name changed")
276 debug(" cls %s. name %s. version %d. was %s. now %s" %
277 cls, name, wire_version,
278 of_g.special_offsets[(cls, name)], last_name)
279 sys.exit(1)
280 of_g.special_offsets[(cls, name)] = last_name
281
282 member["offset"] = offset
283 if m_type.find("list(") == 0:
284 (list_name, base_type) = loxi_utils.list_name_extract(m_type)
285 lists.add(list_name)
286 member["m_type"] = list_name + "_t"
287 offset = -1
288 elif m_type.find("struct") == 0:
289 debug("ERROR found struct: %s.%s " % (cls, name))
290 sys.exit(1)
291 elif m_type == "octets":
292 log("offset gen skipping octets: %s.%s " % (cls, name))
293 offset = -1
294 else:
295 offset, len_update = update_offset(cls, wire_version, name,
296 offset, m_type)
297 if offset != -1:
298 fixed_offset = offset
299 else:
300 fixed_offset += len_update
301 log("offset is -1 for %s.%s version %d " %
302 (cls, name, wire_version))
303 last_offset = offset
304 last_name = name
305 of_g.base_length[(cls, wire_version)] = fixed_offset
306 if (offset != -1):
307 of_g.is_fixed_length.add((cls, wire_version))
308 for list_type in lists:
309 classes[list_type] = []
310 of_g.ordered_classes[wire_version].append(list_type)
311 of_g.base_length[(list_type, wire_version)] = 0
312
313def process_input_file(filename):
314 """
315 Process an input file
316
317 @param filename The input filename
318
319 @returns (wire_version, classes), where wire_version is the integer wire
320 protocol number and classes is the dict of all classes processed from the
321 file.
322
323 @todo Add support for parsing enums like we do structs
324 """
325
326 # Parse the input file
327 try:
328 ast = parser.parse(open(filename, 'r').read())
329 except pyparsing.ParseBaseException as e:
330 print "Parse error in %s: %s" % (os.path.basename(filename), str(e))
331 sys.exit(1)
332
333 ofinput = of_g.OFInput()
334
335 # Now for each structure, generate lists for each member
336 for s in ast:
337 if s[0] == 'struct':
338 name = s[1].replace("ofp_", "of_", 1)
339 members = [dict(m_type=x[0], name=x[1]) for x in s[2]]
340 ofinput.classes[name] = members
341 ofinput.ordered_classes.append(name)
342 if name in type_maps.inheritance_map:
343 # Clone class into header class and add to list
344 ofinput.classes[name + "_header"] = members[:]
345 ofinput.ordered_classes.append(name + "_header")
346 elif s[0] == 'metadata':
347 if s[1] == 'version':
348 log("Found version: wire version " + s[2])
349 if s[2] == 'any':
350 ofinput.wire_versions.update(of_g.wire_ver_map.keys())
351 elif int(s[2]) in of_g.supported_wire_protos:
352 ofinput.wire_versions.add(int(s[2]))
353 else:
354 debug("Unrecognized wire protocol version")
355 sys.exit(1)
356 found_wire_version = True
357
358 if not ofinput.wire_versions:
359 debug("Missing #version metadata")
360 sys.exit(1)
361
362 return ofinput
363
364def order_and_assign_object_ids():
365 """
366 Order all classes and assign object ids to all classes.
367
368 This is done to promote a reasonable order of the objects, putting
369 messages first followed by non-messages. No assumptions should be
370 made about the order, nor about contiguous numbering. However, the
371 numbers should all be reasonably small allowing arrays indexed by
372 these enum values to be defined.
373 """
374
375 # Generate separate message and non-message ordered lists
376 for cls in of_g.unified:
377 if loxi_utils.class_is_message(cls):
378 of_g.ordered_messages.append(cls)
379 elif loxi_utils.class_is_list(cls):
380 of_g.ordered_list_objects.append(cls)
381 else:
382 of_g.ordered_non_messages.append(cls)
383
384 of_g.ordered_pseudo_objects.append("of_stats_request")
385 of_g.ordered_pseudo_objects.append("of_stats_reply")
386 of_g.ordered_pseudo_objects.append("of_flow_mod")
387
388 of_g.ordered_messages.sort()
389 of_g.ordered_pseudo_objects.sort()
390 of_g.ordered_non_messages.sort()
391 of_g.ordered_list_objects.sort()
392 of_g.standard_class_order.extend(of_g.ordered_messages)
393 of_g.standard_class_order.extend(of_g.ordered_non_messages)
394 of_g.standard_class_order.extend(of_g.ordered_list_objects)
395
396 # This includes pseudo classes for which most code is not generated
397 of_g.all_class_order.extend(of_g.ordered_messages)
398 of_g.all_class_order.extend(of_g.ordered_non_messages)
399 of_g.all_class_order.extend(of_g.ordered_list_objects)
400 of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
401
402 # Assign object IDs
403 for cls in of_g.ordered_messages:
404 of_g.unified[cls]["object_id"] = of_g.object_id
405 of_g.object_id += 1
406 for cls in of_g.ordered_non_messages:
407 of_g.unified[cls]["object_id"] = of_g.object_id
408 of_g.object_id += 1
409 for cls in of_g.ordered_list_objects:
410 of_g.unified[cls]["object_id"] = of_g.object_id
411 of_g.object_id += 1
412 for cls in of_g.ordered_pseudo_objects:
413 of_g.unified[cls] = {}
414 of_g.unified[cls]["object_id"] = of_g.object_id
415 of_g.object_id += 1
416
417def process_canonical_file(version, filename):
418 """
419 Read in contents of openflow.h file filename and process it
420 @param version The wire version number
421 @param filename The name of the openflow header file for this version
422
423 Updates of_g.identifiers dictionary. See of_g.py
424 """
425 log("Processing canonical file %s, version %d" % (filename, version))
426 f = open(filename, 'r')
427 all_lines = f.readlines()
428 contents = " ".join(all_lines)
429 identifiers.add_identifiers(of_g.identifiers, of_g.identifiers_by_group,
430 version, contents)
431
432
433def initialize_versions():
434 """
435 Create an empty datastructure for each target version.
436 """
437
438 for wire_version in of_g.target_version_list:
439 version_name = of_g.of_version_wire2name[wire_version]
440 of_g.wire_ver_map[wire_version] = version_name
441 versions[version_name] = dict(
442 version_name = version_name,
443 wire_version = wire_version,
444 classes = {})
445 of_g.ordered_classes[wire_version] = []
446
447
448def read_input():
449 """
450 Read in from files given on command line and update global state
451
452 @fixme Should select versions to support from command line
453 """
454
455 filenames = sorted(glob.glob("%s/openflow_input/*" % root_dir))
456
457 for filename in filenames:
458 log("Processing struct file: " + filename)
459 ofinput = process_input_file(filename)
460
461 # Populate global state
462 for wire_version in ofinput.wire_versions:
463 version_name = of_g.of_version_wire2name[wire_version]
464 versions[version_name]['classes'].update(copy.deepcopy(ofinput.classes))
465 of_g.ordered_classes[wire_version].extend(ofinput.ordered_classes)
466
467def add_extra_classes():
468 """
469 Add classes that are generated by Python code instead of from the
470 input files.
471 """
472
473 for wire_version in [of_g.VERSION_1_2, of_g.VERSION_1_3]:
474 version_name = of_g.of_version_wire2name[wire_version]
475 oxm.add_oxm_classes_1_2(versions[version_name]['classes'], wire_version)
476
477def analyze_input():
478 """
479 Add information computed from the input, including offsets and
480 lengths of struct members and the set of list and action_id types.
481 """
482
483 # Generate action_id classes for OF 1.3
484 for wire_version, ordered_classes in of_g.ordered_classes.items():
485 if not wire_version in [of_g.VERSION_1_3]:
486 continue
487 classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
488 for cls in ordered_classes:
489 if not loxi_utils.class_is_action(cls):
490 continue
491 action = cls[10:]
492 if action == '' or action == 'header':
493 continue
494 name = "of_action_id_" + action
495 members = classes["of_action"][:]
496 of_g.ordered_classes[wire_version].append(name)
497 if type_maps.action_id_is_extension(name, wire_version):
498 # Copy the base action classes thru subtype
499 members = classes["of_action_" + action][:4]
500 classes[name] = members
501
502 # @fixme If we support extended actions in OF 1.3, need to add IDs
503 # for them here
504
505 for wire_version in of_g.wire_ver_map.keys():
506 version_name = of_g.of_version_wire2name[wire_version]
507 calculate_offsets_and_lengths(
508 of_g.ordered_classes[wire_version],
509 versions[version_name]['classes'],
510 wire_version)
511
512def unify_input():
513 """
514 Create Unified View of Objects
515 """
516
517 global versions
518
519 # Add classes to unified in wire-format order so that it is easier
520 # to generate things later
521 keys = versions.keys()
522 keys.sort(reverse=True)
523 for version in keys:
524 wire_version = versions[version]["wire_version"]
525 classes = versions[version]["classes"]
526 for cls in of_g.ordered_classes[wire_version]:
527 add_class(wire_version, cls, classes[cls])
528
529
530def log_all_class_info():
531 """
532 Log the results of processing the input
533
534 Debug function
535 """
536
537 for cls in of_g.unified:
538 for v in of_g.unified[cls]:
539 if type(v) == type(0):
540 log("cls: %s. ver: %d. base len %d. %s" %
541 (str(cls), v, of_g.base_length[(cls, v)],
542 loxi_utils.class_is_var_len(cls,v) and "not fixed"
543 or "fixed"))
544 if "use_version" in of_g.unified[cls][v]:
545 log("cls %s: v %d mapped to %d" % (str(cls), v,
546 of_g.unified[cls][v]["use_version"]))
547 if "members" in of_g.unified[cls][v]:
548 for member in of_g.unified[cls][v]["members"]:
549 log(" %-20s: type %-20s. offset %3d" %
550 (member["name"], member["m_type"],
551 member["offset"]))
552
553def generate_all_files():
554 """
555 Create the files for the language target
556 """
557 for (name, fn) in lang_module.targets.items():
558 path = of_g.options.install_dir + '/' + name
559 os.system("mkdir -p %s" % os.path.dirname(path))
560 with open(path, "w") as outfile:
561 fn(outfile, os.path.basename(name))
562 print("Wrote contents for " + name)
563
564if __name__ == '__main__':
565 of_g.loxigen_log_file = open("loxigen.log", "w")
566 of_g.loxigen_dbg_file = sys.stdout
567
568 of_g.process_commandline()
569 # @fixme Use command line params to select log
570
571 if not config_sanity_check():
572 debug("Config sanity check failed\n")
573 sys.exit(1)
574
575 # Import the language file
576 lang_file = "lang_%s" % of_g.options.lang
577 lang_module = __import__(lang_file)
578
579 # If list files, just list auto-gen files to stdout and exit
580 if of_g.options.list_files:
581 for name in lang_module.targets:
582 print of_g.options.install_dir + '/' + name
583 sys.exit(0)
584
585 log("\nGenerating files for target language %s\n" % of_g.options.lang)
586
587 # Generate identifier code
588 for version in of_g.target_version_list:
589 version_tag = of_g.param_version_names[version]
590 filename = "%s/canonical/openflow.h-%s" % (root_dir, version_tag)
591 process_canonical_file(version, filename)
592
593 initialize_versions()
594 read_input()
595 add_extra_classes()
596 analyze_input()
597 unify_input()
598 order_and_assign_object_ids()
599 log_all_class_info()
600 generate_all_files()