blob: 6e536fabbc37b2ab892932e1751d6b599091ea18 [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:
320 if ofclass.name in ("of_group_add", "of_group_modify", "of_group_delete"):
321 continue
322 of_g.ordered_classes[wire_version].append(ofclass.name)
323 legacy_members = []
324 pad_count = 0
325 for m in ofclass.members:
326 if type(m) == OFPadMember:
327 m_name = 'pad%d' % pad_count
328 if m_name == 'pad0': m_name = 'pad'
329 legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
330 name=m_name))
331 pad_count += 1
332 else:
333 # HACK the C backend does not yet support of_oxm_t
334 if m.oftype == 'of_oxm_t':
335 m_type = 'of_octets_t'
336 else:
337 enum = find(lambda e: e.name == m.oftype, protocol.enums)
338 if enum and "wire_type" in enum.params:
339 m_type = enum.params["wire_type"]
340 else:
341 m_type = m.oftype
342 legacy_members.append(dict(m_type=m_type, name=m.name))
343 versions[version_name]['classes'][ofclass.name] = legacy_members
344
345 for enum in protocol.enums:
346 for entry in enum.entries:
347 identifiers.add_identifier(
348 translation.loxi_name(entry.name),
349 entry.name, enum.name, entry.value, wire_version,
350 of_g.identifiers, of_g.identifiers_by_group)
351
352def populate_type_maps():
353 """
354 Use the type members in the IR to fill out the legacy type_maps.
355 """
356
357 def split_inherited_cls(cls):
358 if cls == 'of_meter_band_stats': # HACK not a subtype of of_meter_band
359 return None, None
360 for parent in sorted(type_maps.inheritance_data.keys(), reverse=True):
361 if cls.startswith(parent):
362 return (parent, cls[len(parent)+1:])
363 return None, None
364
365 def find_experimenter(parent, cls):
366 for experimenter in sorted(of_g.experimenter_name_to_id.keys(), reverse=True):
367 prefix = parent + '_' + experimenter
368 if cls.startswith(prefix) and cls != prefix:
369 return experimenter
370 return None
371
372 def find_type_value(ofclass, m_name):
373 for m in ofclass.members:
374 if isinstance(m, OFTypeMember) and m.name == m_name:
375 return m.value
376 raise KeyError("ver=%d, cls=%s, m_name=%s" % (wire_version, cls, m_name))
377
378 # Most inheritance classes: actions, instructions, etc
379 for version, protocol in loxi_globals.ir.items():
380 wire_version = version.wire_version
381 for ofclass in protocol.classes:
382 cls = ofclass.name
383 parent, subcls = split_inherited_cls(cls)
384 if not (parent and subcls):
385 continue
386 if parent == 'of_oxm':
387 type_len = find_type_value(ofclass, 'type_len')
388 oxm_class = (type_len >> 16) & 0xffff
389 if oxm_class != 0x8000:
390 # Do not include experimenter OXMs in the main table
391 val = type_maps.invalid_type
392 else:
393 val = (type_len >> 8) & 0xff
394 else:
395 val = find_type_value(ofclass, 'type')
396 type_maps.inheritance_data[parent][wire_version][subcls] = val
397
398 # Extensions (only actions for now)
399 experimenter = find_experimenter(parent, cls)
400 if parent == 'of_action' and experimenter:
401 val = find_type_value(ofclass, 'subtype')
402 type_maps.extension_action_subtype[wire_version][experimenter][cls] = val
403 if wire_version >= of_g.VERSION_1_3:
404 cls2 = parent + "_id" + cls[len(parent):]
405 type_maps.extension_action_id_subtype[wire_version][experimenter][cls2] = val
406
407 # Messages
408 for version, protocol in loxi_globals.ir.items():
409 wire_version = version.wire_version
410 for ofclass in protocol.classes:
411 cls = ofclass.name
412 # HACK (though this is what loxi_utils.class_is_message() does)
413 if not [x for x in ofclass.members if isinstance(x, OFDataMember) and x.name == 'xid']:
414 continue
415 if type_maps.class_is_virtual(cls):
416 continue
417 # HACK hide of_group subclasses from legacy c backend
418 if ofclass.name in ("of_group_add", "of_group_modify", "of_group_delete"):
419 continue
420 subcls = cls[3:]
421 val = find_type_value(ofclass, 'type')
422 if not val in type_maps.message_types[wire_version].values():
423 type_maps.message_types[wire_version][subcls] = val
424
425 # Extensions
426 experimenter = find_experimenter('of', cls)
427 if experimenter:
428 val = find_type_value(ofclass, 'subtype')
429 type_maps.extension_message_subtype[wire_version][experimenter][cls] = val
430
431 type_maps.generate_maps()
432
433def analyze_input():
434 """
435 Add information computed from the input, including offsets and
436 lengths of struct members and the set of list and action_id types.
437 """
438
439 # Generate header classes for inheritance parents
440 for wire_version, ordered_classes in of_g.ordered_classes.items():
441 classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
442 for cls in ordered_classes:
443 if cls in type_maps.inheritance_map:
444 new_cls = cls + '_header'
445 of_g.ordered_classes[wire_version].append(new_cls)
446 classes[new_cls] = classes[cls]
447
448 # Generate action_id classes for OF 1.3
449 for wire_version, ordered_classes in of_g.ordered_classes.items():
450 if not wire_version in [of_g.VERSION_1_3]:
451 continue
452 classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
453 for cls in ordered_classes:
454 if not loxi_utils.class_is_action(cls):
455 continue
456 action = cls[10:]
457 if action == '' or action == 'header':
458 continue
459 name = "of_action_id_" + action
460 members = classes["of_action"][:]
461 of_g.ordered_classes[wire_version].append(name)
462 if type_maps.action_id_is_extension(name, wire_version):
463 # Copy the base action classes thru subtype
464 members = classes["of_action_" + action][:4]
465 classes[name] = members
466
467 # @fixme If we support extended actions in OF 1.3, need to add IDs
468 # for them here
469
470 for wire_version in of_g.wire_ver_map.keys():
471 version_name = of_g.of_version_wire2name[wire_version]
472 calculate_offsets_and_lengths(
473 of_g.ordered_classes[wire_version],
474 versions[version_name]['classes'],
475 wire_version)
476
477def unify_input():
478 """
479 Create Unified View of Objects
480 """
481
482 global versions
483
484 # Add classes to unified in wire-format order so that it is easier
485 # to generate things later
486 keys = versions.keys()
487 keys.sort(reverse=True)
488 for version in keys:
489 wire_version = versions[version]["wire_version"]
490 classes = versions[version]["classes"]
491 for cls in of_g.ordered_classes[wire_version]:
492 add_class(wire_version, cls, classes[cls])
493
494
495def log_all_class_info():
496 """
497 Log the results of processing the input
498
499 Debug function
500 """
501
502 for cls in of_g.unified:
503 for v in of_g.unified[cls]:
504 if type(v) == type(0):
505 log("cls: %s. ver: %d. base len %d. %s" %
506 (str(cls), v, of_g.base_length[(cls, v)],
507 loxi_utils.class_is_var_len(cls,v) and "not fixed"
508 or "fixed"))
509 if "use_version" in of_g.unified[cls][v]:
510 log("cls %s: v %d mapped to %d" % (str(cls), v,
511 of_g.unified[cls][v]["use_version"]))
512 if "members" in of_g.unified[cls][v]:
513 for member in of_g.unified[cls][v]["members"]:
514 log(" %-20s: type %-20s. offset %3d" %
515 (member["name"], member["m_type"],
516 member["offset"]))
517
518def generate_all_files():
519 """
520 Create the files for the language target
521 """
522 for (name, fn) in lang_module.targets.items():
523 path = of_g.options.install_dir + '/' + name
524 os.system("mkdir -p %s" % os.path.dirname(path))
525 with open(path, "w") as outfile:
526 fn(outfile, os.path.basename(name))
527 print("Wrote contents for " + name)
528
529if __name__ == '__main__':
530 of_g.loxigen_log_file = open("loxigen.log", "w")
531 of_g.loxigen_dbg_file = sys.stdout
532
533 of_g.process_commandline()
534 # @fixme Use command line params to select log
535
536 if not config_sanity_check():
537 debug("Config sanity check failed\n")
538 sys.exit(1)
539
540 # Import the language file
541 lang_file = "lang_%s" % of_g.options.lang
542 lang_module = __import__(lang_file)
543
544 # If list files, just list auto-gen files to stdout and exit
545 if of_g.options.list_files:
546 for name in lang_module.targets:
547 print of_g.options.install_dir + '/' + name
548 sys.exit(0)
549
550 log("\nGenerating files for target language %s\n" % of_g.options.lang)
551
552 initialize_versions()
553 read_input()
554 populate_type_maps()
555 analyze_input()
556 unify_input()
557 order_and_assign_object_ids()
558 log_all_class_info()
559 generate_all_files()