blob: f117bfddb4b3d7c432077cae4b87478ebce89693 [file] [log] [blame]
Andreas Wundsam76db0062013-11-15 13:34:41 -08001# Copyright 2013, Big Switch Networks, Inc.
2#
3# LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
4# the following special exception:
5#
6# LOXI Exception
7#
8# As a special exception to the terms of the EPL, you may distribute libraries
9# generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
10# that copyright and licensing notices generated by LoxiGen are not altered or removed
11# from the LoxiGen Libraries and the notice provided below is (i) included in
12# the LoxiGen Libraries, if distributed in source code form and (ii) included in any
13# documentation for the LoxiGen Libraries, if distributed in binary form.
14#
15# Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
16#
17# You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
18# a copy of the EPL at:
19#
20# http://www.eclipse.org/legal/epl-v10.html
21#
22# Unless required by applicable law or agreed to in writing, software
23# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
24# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
25# EPL for the specific language governing permissions and limitations
26# under the EPL.
27
28import sys
29
30import re
31import string
32import os
33import glob
34import copy
35import collections
36import c_gen.of_g_legacy as of_g
37import c_gen.type_maps as type_maps
38import c_gen.loxi_utils_legacy as loxi_utils
39import loxi_globals
40import c_gen.identifiers as identifiers
41import pyparsing
42import loxi_front_end.parser as parser
43import c_gen.translation as translation
44import loxi_front_end.frontend as frontend
45from loxi_ir import *
46from generic_utils import *
47
48root_dir = os.path.dirname(os.path.realpath(__file__))
49
50versions = {}
51# TODO: Put these in a class so they get documented
52
53## Dict indexed by version giving all info related to version
54#
55# This is local; after processing, the information is stored in
56# of_g variables.
57
58def add_class(wire_version, cls, members):
59 """
60 Process a class for the given version and update the unified
61 list of classes as needed.
62
63 @param wire_version The wire version for this class defn
64 @param cls The name of the class being added
65 @param members The list of members with offsets calculated
66 """
67 memid = 0
68
69 sig = loxi_utils.class_signature(members)
70 if cls in of_g.unified:
71 uc = of_g.unified[cls]
72 if wire_version in uc:
73 debug("Error adding %s to unified. Wire ver %d exists" %
74 (cls, wire_version))
75 sys.exit(1)
76 uc[wire_version] = {}
77 # Check for a matching signature
78 for wver in uc:
79 if type(wver) != type(0): continue
80 if wver == wire_version: continue
81 if not "use_version" in uc[wver]:
82 if sig == loxi_utils.class_signature(uc[wver]["members"]):
83 log("Matched %s, ver %d to ver %d" %
84 (cls, wire_version, wver))
85 # have a match with existing version
86 uc[wire_version]["use_version"] = wver
87 # What else to do?
88 return
89 else: # Haven't seen this entry before
90 log("Adding %s to unified list, ver %d" % (cls, wire_version))
91 of_g.unified[cls] = dict(union={})
92 uc = of_g.unified[cls]
93
94 # At this point, need to add members for this version
95 uc[wire_version] = dict(members = members)
96
97 # Per member processing:
98 # Add to union list (I'm sure there's a better way)
99 # Check if it's a list
100 union = uc["union"]
101 if not cls in of_g.ordered_members:
102 of_g.ordered_members[cls] = []
103 for member in members:
104 m_name = member["name"]
105 m_type = member["m_type"]
106 if m_name.find("pad") == 0:
107 continue
108 if m_name in union:
109 if not m_type == union[m_name]["m_type"]:
110 debug("ERROR: CLASS: %s. VERSION %d. MEMBER: %s. TYPE: %s" %
111 (cls, wire_version, m_name, m_type))
112 debug(" Type conflict adding member to unified set.")
113 debug(" Current union[%s]:" % m_name)
114 debug(union[m_name])
115 sys.exit(1)
116 else:
117 union[m_name] = dict(m_type=m_type, memid=memid)
118 memid += 1
119 if not m_name in of_g.ordered_members[cls]:
120 of_g.ordered_members[cls].append(m_name)
121
122def update_offset(cls, wire_version, name, offset, m_type):
123 """
124 Update (and return) the offset based on type.
125 @param cls The parent class
126 @param wire_version The wire version being processed
127 @param name The name of the data member
128 @param offset The current offset
129 @param m_type The type declaration being processed
130 @returns A pair (next_offset, len_update) next_offset is the new offset
131 of the next object or -1 if this is a var-length object. len_update
132 is the increment that should be added to the length. Note that (for
133 of_match_v3) it is variable length, but it adds 8 bytes to the fixed
134 length of the object
135 If offset is already -1, do not update
136 Otherwise map to base type and count and update (if possible)
137 """
138 if offset < 0: # Don't update offset once set to -1
139 return offset, 0
140
141 count, base_type = loxi_utils.type_dec_to_count_base(m_type)
142
143 len_update = 0
144 if base_type in of_g.of_mixed_types:
145 base_type = of_g.of_mixed_types[base_type][wire_version]
146
147 base_class = base_type[:-2]
148 if (base_class, wire_version) in of_g.is_fixed_length:
149 bytes = of_g.base_length[(base_class, wire_version)]
150 else:
151 if base_type == "of_match_v3_t":
152 # This is a special case: it has non-zero min length
153 # but is variable length
154 bytes = -1
155 len_update = 8
156 elif base_type in of_g.of_base_types:
157 bytes = of_g.of_base_types[base_type]["bytes"]
158 else:
159 print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
160 log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
161 bytes = -1
162
163 # If bytes
164 if bytes > 0:
165 len_update = count * bytes
166
167 if bytes == -1:
168 return -1, len_update
169
170 return offset + (count * bytes), len_update
171
172def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
173 """
174 Generate the offsets for fixed offset class members
175 Also calculate the class_sizes when possible.
176
177 @param classes The classes to process
178 @param wire_version The wire version for this set of classes
179
180 Updates global variables
181 """
182
183 lists = set()
184
185 # Generate offsets
186 for cls in ordered_classes:
187 fixed_offset = 0 # The last "good" offset seen
188 offset = 0
189 last_offset = 0
190 last_name = "-"
191 for member in classes[cls]:
192 m_type = member["m_type"]
193 name = member["name"]
194 if last_offset == -1:
195 if name == "pad":
196 log("Skipping pad for special offset for %s" % cls)
197 else:
198 log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
199 (name, last_name, cls, wire_version))
200 if (((cls, name) in of_g.special_offsets) and
201 (of_g.special_offsets[(cls, name)] != last_name)):
202 debug("ERROR: special offset prev name changed")
203 debug(" cls %s. name %s. version %d. was %s. now %s" %
204 cls, name, wire_version,
205 of_g.special_offsets[(cls, name)], last_name)
206 sys.exit(1)
207 of_g.special_offsets[(cls, name)] = last_name
208
209 member["offset"] = offset
210 if m_type.find("list(") == 0:
211 (list_name, base_type) = loxi_utils.list_name_extract(m_type)
212 lists.add(list_name)
213 member["m_type"] = list_name + "_t"
214 offset = -1
215 elif m_type.find("struct") == 0:
216 debug("ERROR found struct: %s.%s " % (cls, name))
217 sys.exit(1)
218 elif m_type == "octets":
219 log("offset gen skipping octets: %s.%s " % (cls, name))
220 offset = -1
221 else:
222 offset, len_update = update_offset(cls, wire_version, name,
223 offset, m_type)
224 if offset != -1:
225 fixed_offset = offset
226 else:
227 fixed_offset += len_update
228 log("offset is -1 for %s.%s version %d " %
229 (cls, name, wire_version))
230 last_offset = offset
231 last_name = name
232 of_g.base_length[(cls, wire_version)] = fixed_offset
233 if (offset != -1):
234 of_g.is_fixed_length.add((cls, wire_version))
235 for list_type in lists:
236 classes[list_type] = []
237 of_g.ordered_classes[wire_version].append(list_type)
238 of_g.base_length[(list_type, wire_version)] = 0
239
240def order_and_assign_object_ids():
241 """
242 Order all classes and assign object ids to all classes.
243
244 This is done to promote a reasonable order of the objects, putting
245 messages first followed by non-messages. No assumptions should be
246 made about the order, nor about contiguous numbering. However, the
247 numbers should all be reasonably small allowing arrays indexed by
248 these enum values to be defined.
249 """
250
251 # Generate separate message and non-message ordered lists
252 for cls in of_g.unified:
253 if loxi_utils.class_is_message(cls):
254 of_g.ordered_messages.append(cls)
255 elif loxi_utils.class_is_list(cls):
256 of_g.ordered_list_objects.append(cls)
257 else:
258 of_g.ordered_non_messages.append(cls)
259
260 of_g.ordered_messages.sort()
261 of_g.ordered_pseudo_objects.sort()
262 of_g.ordered_non_messages.sort()
263 of_g.ordered_list_objects.sort()
264 of_g.standard_class_order.extend(of_g.ordered_messages)
265 of_g.standard_class_order.extend(of_g.ordered_non_messages)
266 of_g.standard_class_order.extend(of_g.ordered_list_objects)
267
268 # This includes pseudo classes for which most code is not generated
269 of_g.all_class_order.extend(of_g.ordered_messages)
270 of_g.all_class_order.extend(of_g.ordered_non_messages)
271 of_g.all_class_order.extend(of_g.ordered_list_objects)
272 of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
273
274 # Assign object IDs
275 for cls in of_g.ordered_messages:
276 of_g.unified[cls]["object_id"] = of_g.object_id
277 of_g.object_id += 1
278 for cls in of_g.ordered_non_messages:
279 of_g.unified[cls]["object_id"] = of_g.object_id
280 of_g.object_id += 1
281 for cls in of_g.ordered_list_objects:
282 of_g.unified[cls]["object_id"] = of_g.object_id
283 of_g.object_id += 1
284 for cls in of_g.ordered_pseudo_objects:
285 of_g.unified[cls] = {}
286 of_g.unified[cls]["object_id"] = of_g.object_id
287 of_g.object_id += 1
288
289
290def initialize_versions():
291 """
292 Create an empty datastructure for each target version.
293 """
294
295 for version in loxi_globals.OFVersions.target_versions:
296 wire_version = version.wire_version
297 version_name = of_g.of_version_wire2name[wire_version]
298 of_g.wire_ver_map[wire_version] = version_name
299 versions[version_name] = dict(
300 version_name = version_name,
301 wire_version = wire_version,
302 classes = {})
303 of_g.ordered_classes[wire_version] = []
304
305 of_g.target_version_list = [ v.wire_version for v in loxi_globals.OFVersions.target_versions ]
306
307def build_ordered_classes():
308 """
309 Read in from files given on command line and update global state
310
311 @fixme Should select versions to support from command line
312 """
313
314 for version, protocol in loxi_globals.ir.items():
315 wire_version = version.wire_version
316 # Populate global state
317 version_name = of_g.of_version_wire2name[wire_version]
318
319 for ofclass in protocol.classes:
Andreas Wundsam76db0062013-11-15 13:34:41 -0800320 of_g.ordered_classes[wire_version].append(ofclass.name)
321 legacy_members = []
322 pad_count = 0
323 for m in ofclass.members:
324 if type(m) == OFPadMember:
325 m_name = 'pad%d' % pad_count
326 if m_name == 'pad0': m_name = 'pad'
327 legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
328 name=m_name))
329 pad_count += 1
330 else:
331 # HACK the C backend does not yet support of_oxm_t
332 if m.oftype == 'of_oxm_t':
333 m_type = 'of_octets_t'
334 else:
335 enum = find(lambda e: e.name == m.oftype, protocol.enums)
336 if enum and "wire_type" in enum.params:
337 m_type = enum.params["wire_type"]
338 else:
339 m_type = m.oftype
340 legacy_members.append(dict(m_type=m_type, name=m.name))
341 versions[version_name]['classes'][ofclass.name] = legacy_members
342
343 for enum in protocol.enums:
344 for entry in enum.entries:
345 identifiers.add_identifier(
346 translation.loxi_name(entry.name),
347 entry.name, enum.name, entry.value, wire_version,
348 of_g.identifiers, of_g.identifiers_by_group)
349
350def populate_type_maps():
351 """
352 Use the type members in the IR to fill out the legacy type_maps.
353 """
354
355 def split_inherited_cls(cls):
356 if cls == 'of_meter_band_stats': # HACK not a subtype of of_meter_band
357 return None, None
358 for parent in sorted(type_maps.inheritance_data.keys(), reverse=True):
359 if cls.startswith(parent):
360 return (parent, cls[len(parent)+1:])
361 return None, None
362
363 def find_experimenter(parent, cls):
364 for experimenter in sorted(of_g.experimenter_name_to_id.keys(), reverse=True):
365 prefix = parent + '_' + experimenter
366 if cls.startswith(prefix) and cls != prefix:
367 return experimenter
368 return None
369
370 def find_type_value(ofclass, m_name):
371 for m in ofclass.members:
372 if isinstance(m, OFTypeMember) and m.name == m_name:
373 return m.value
374 raise KeyError("ver=%d, cls=%s, m_name=%s" % (wire_version, cls, m_name))
375
376 # Most inheritance classes: actions, instructions, etc
377 for version, protocol in loxi_globals.ir.items():
378 wire_version = version.wire_version
379 for ofclass in protocol.classes:
380 cls = ofclass.name
381 parent, subcls = split_inherited_cls(cls)
382 if not (parent and subcls):
383 continue
384 if parent == 'of_oxm':
385 type_len = find_type_value(ofclass, 'type_len')
386 oxm_class = (type_len >> 16) & 0xffff
387 if oxm_class != 0x8000:
388 # Do not include experimenter OXMs in the main table
389 val = type_maps.invalid_type
390 else:
391 val = (type_len >> 8) & 0xff
392 else:
393 val = find_type_value(ofclass, 'type')
394 type_maps.inheritance_data[parent][wire_version][subcls] = val
395
396 # Extensions (only actions for now)
397 experimenter = find_experimenter(parent, cls)
398 if parent == 'of_action' and experimenter:
399 val = find_type_value(ofclass, 'subtype')
400 type_maps.extension_action_subtype[wire_version][experimenter][cls] = val
401 if wire_version >= of_g.VERSION_1_3:
402 cls2 = parent + "_id" + cls[len(parent):]
403 type_maps.extension_action_id_subtype[wire_version][experimenter][cls2] = val
Rich Laneef7b9942013-11-18 16:29:28 -0800404 elif parent == 'of_instruction' and experimenter:
405 val = find_type_value(ofclass, 'subtype')
406 type_maps.extension_instruction_subtype[wire_version][experimenter][cls] = val
Andreas Wundsam76db0062013-11-15 13:34:41 -0800407
408 # Messages
409 for version, protocol in loxi_globals.ir.items():
410 wire_version = version.wire_version
411 for ofclass in protocol.classes:
412 cls = ofclass.name
413 # HACK (though this is what loxi_utils.class_is_message() does)
414 if not [x for x in ofclass.members if isinstance(x, OFDataMember) and x.name == 'xid']:
415 continue
416 if type_maps.class_is_virtual(cls):
417 continue
Andreas Wundsam76db0062013-11-15 13:34:41 -0800418 subcls = cls[3:]
419 val = find_type_value(ofclass, 'type')
420 if not val in type_maps.message_types[wire_version].values():
421 type_maps.message_types[wire_version][subcls] = val
422
423 # Extensions
424 experimenter = find_experimenter('of', cls)
Andreas Wundsam2c0a2d72013-11-15 15:16:36 -0800425 if experimenter and ofclass.is_subclassof("of_experimenter"):
Andreas Wundsam76db0062013-11-15 13:34:41 -0800426 val = find_type_value(ofclass, 'subtype')
427 type_maps.extension_message_subtype[wire_version][experimenter][cls] = val
428
429 type_maps.generate_maps()
430
431def analyze_input():
432 """
433 Add information computed from the input, including offsets and
434 lengths of struct members and the set of list and action_id types.
435 """
436
437 # Generate header classes for inheritance parents
438 for wire_version, ordered_classes in of_g.ordered_classes.items():
439 classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
440 for cls in ordered_classes:
441 if cls in type_maps.inheritance_map:
442 new_cls = cls + '_header'
443 of_g.ordered_classes[wire_version].append(new_cls)
444 classes[new_cls] = classes[cls]
445
Andreas Wundsam76db0062013-11-15 13:34:41 -0800446 for wire_version in of_g.wire_ver_map.keys():
447 version_name = of_g.of_version_wire2name[wire_version]
448 calculate_offsets_and_lengths(
449 of_g.ordered_classes[wire_version],
450 versions[version_name]['classes'],
451 wire_version)
452
453def unify_input():
454 """
455 Create Unified View of Objects
456 """
457
458 global versions
459
460 # Add classes to unified in wire-format order so that it is easier
461 # to generate things later
462 keys = versions.keys()
463 keys.sort(reverse=True)
464 for version in keys:
465 wire_version = versions[version]["wire_version"]
466 classes = versions[version]["classes"]
467 for cls in of_g.ordered_classes[wire_version]:
468 add_class(wire_version, cls, classes[cls])
469
470
471def log_all_class_info():
472 """
473 Log the results of processing the input
474
475 Debug function
476 """
477
478 for cls in of_g.unified:
479 for v in of_g.unified[cls]:
480 if type(v) == type(0):
481 log("cls: %s. ver: %d. base len %d. %s" %
482 (str(cls), v, of_g.base_length[(cls, v)],
483 loxi_utils.class_is_var_len(cls,v) and "not fixed"
484 or "fixed"))
485 if "use_version" in of_g.unified[cls][v]:
486 log("cls %s: v %d mapped to %d" % (str(cls), v,
487 of_g.unified[cls][v]["use_version"]))
488 if "members" in of_g.unified[cls][v]:
489 for member in of_g.unified[cls][v]["members"]:
490 log(" %-20s: type %-20s. offset %3d" %
491 (member["name"], member["m_type"],
492 member["offset"]))
493
494def generate_all_files():
495 """
496 Create the files for the language target
497 """
498 for (name, fn) in lang_module.targets.items():
499 path = of_g.options.install_dir + '/' + name
500 os.system("mkdir -p %s" % os.path.dirname(path))
501 with open(path, "w") as outfile:
502 fn(outfile, os.path.basename(name))
503 print("Wrote contents for " + name)
504
505if __name__ == '__main__':
506 of_g.loxigen_log_file = open("loxigen.log", "w")
507 of_g.loxigen_dbg_file = sys.stdout
508
509 of_g.process_commandline()
510 # @fixme Use command line params to select log
511
512 if not config_sanity_check():
513 debug("Config sanity check failed\n")
514 sys.exit(1)
515
516 # Import the language file
517 lang_file = "lang_%s" % of_g.options.lang
518 lang_module = __import__(lang_file)
519
520 # If list files, just list auto-gen files to stdout and exit
521 if of_g.options.list_files:
522 for name in lang_module.targets:
523 print of_g.options.install_dir + '/' + name
524 sys.exit(0)
525
526 log("\nGenerating files for target language %s\n" % of_g.options.lang)
527
528 initialize_versions()
529 read_input()
530 populate_type_maps()
531 analyze_input()
532 unify_input()
533 order_and_assign_object_ids()
534 log_all_class_info()
535 generate_all_files()