blob: 46aeddaf0e554324f14a91a81269eef72c694f17 [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
Rich Laneef7b9942013-11-18 16:29:28 -0800406 elif parent == 'of_instruction' and experimenter:
407 val = find_type_value(ofclass, 'subtype')
408 type_maps.extension_instruction_subtype[wire_version][experimenter][cls] = val
Andreas Wundsam76db0062013-11-15 13:34:41 -0800409
410 # Messages
411 for version, protocol in loxi_globals.ir.items():
412 wire_version = version.wire_version
413 for ofclass in protocol.classes:
414 cls = ofclass.name
415 # HACK (though this is what loxi_utils.class_is_message() does)
416 if not [x for x in ofclass.members if isinstance(x, OFDataMember) and x.name == 'xid']:
417 continue
418 if type_maps.class_is_virtual(cls):
419 continue
420 # HACK hide of_group subclasses from legacy c backend
421 if ofclass.name in ("of_group_add", "of_group_modify", "of_group_delete"):
422 continue
423 subcls = cls[3:]
424 val = find_type_value(ofclass, 'type')
425 if not val in type_maps.message_types[wire_version].values():
426 type_maps.message_types[wire_version][subcls] = val
427
428 # Extensions
429 experimenter = find_experimenter('of', cls)
Andreas Wundsam2c0a2d72013-11-15 15:16:36 -0800430 if experimenter and ofclass.is_subclassof("of_experimenter"):
Andreas Wundsam76db0062013-11-15 13:34:41 -0800431 val = find_type_value(ofclass, 'subtype')
432 type_maps.extension_message_subtype[wire_version][experimenter][cls] = val
433
434 type_maps.generate_maps()
435
436def analyze_input():
437 """
438 Add information computed from the input, including offsets and
439 lengths of struct members and the set of list and action_id types.
440 """
441
442 # Generate header classes for inheritance parents
443 for wire_version, ordered_classes in of_g.ordered_classes.items():
444 classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
445 for cls in ordered_classes:
446 if cls in type_maps.inheritance_map:
447 new_cls = cls + '_header'
448 of_g.ordered_classes[wire_version].append(new_cls)
449 classes[new_cls] = classes[cls]
450
451 # Generate action_id classes for OF 1.3
452 for wire_version, ordered_classes in of_g.ordered_classes.items():
453 if not wire_version in [of_g.VERSION_1_3]:
454 continue
455 classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
456 for cls in ordered_classes:
457 if not loxi_utils.class_is_action(cls):
458 continue
459 action = cls[10:]
460 if action == '' or action == 'header':
461 continue
462 name = "of_action_id_" + action
463 members = classes["of_action"][:]
464 of_g.ordered_classes[wire_version].append(name)
465 if type_maps.action_id_is_extension(name, wire_version):
466 # Copy the base action classes thru subtype
467 members = classes["of_action_" + action][:4]
468 classes[name] = members
469
470 # @fixme If we support extended actions in OF 1.3, need to add IDs
471 # for them here
472
473 for wire_version in of_g.wire_ver_map.keys():
474 version_name = of_g.of_version_wire2name[wire_version]
475 calculate_offsets_and_lengths(
476 of_g.ordered_classes[wire_version],
477 versions[version_name]['classes'],
478 wire_version)
479
480def unify_input():
481 """
482 Create Unified View of Objects
483 """
484
485 global versions
486
487 # Add classes to unified in wire-format order so that it is easier
488 # to generate things later
489 keys = versions.keys()
490 keys.sort(reverse=True)
491 for version in keys:
492 wire_version = versions[version]["wire_version"]
493 classes = versions[version]["classes"]
494 for cls in of_g.ordered_classes[wire_version]:
495 add_class(wire_version, cls, classes[cls])
496
497
498def log_all_class_info():
499 """
500 Log the results of processing the input
501
502 Debug function
503 """
504
505 for cls in of_g.unified:
506 for v in of_g.unified[cls]:
507 if type(v) == type(0):
508 log("cls: %s. ver: %d. base len %d. %s" %
509 (str(cls), v, of_g.base_length[(cls, v)],
510 loxi_utils.class_is_var_len(cls,v) and "not fixed"
511 or "fixed"))
512 if "use_version" in of_g.unified[cls][v]:
513 log("cls %s: v %d mapped to %d" % (str(cls), v,
514 of_g.unified[cls][v]["use_version"]))
515 if "members" in of_g.unified[cls][v]:
516 for member in of_g.unified[cls][v]["members"]:
517 log(" %-20s: type %-20s. offset %3d" %
518 (member["name"], member["m_type"],
519 member["offset"]))
520
521def generate_all_files():
522 """
523 Create the files for the language target
524 """
525 for (name, fn) in lang_module.targets.items():
526 path = of_g.options.install_dir + '/' + name
527 os.system("mkdir -p %s" % os.path.dirname(path))
528 with open(path, "w") as outfile:
529 fn(outfile, os.path.basename(name))
530 print("Wrote contents for " + name)
531
532if __name__ == '__main__':
533 of_g.loxigen_log_file = open("loxigen.log", "w")
534 of_g.loxigen_dbg_file = sys.stdout
535
536 of_g.process_commandline()
537 # @fixme Use command line params to select log
538
539 if not config_sanity_check():
540 debug("Config sanity check failed\n")
541 sys.exit(1)
542
543 # Import the language file
544 lang_file = "lang_%s" % of_g.options.lang
545 lang_module = __import__(lang_file)
546
547 # If list files, just list auto-gen files to stdout and exit
548 if of_g.options.list_files:
549 for name in lang_module.targets:
550 print of_g.options.install_dir + '/' + name
551 sys.exit(0)
552
553 log("\nGenerating files for target language %s\n" % of_g.options.lang)
554
555 initialize_versions()
556 read_input()
557 populate_type_maps()
558 analyze_input()
559 unify_input()
560 order_and_assign_object_ids()
561 log_all_class_info()
562 generate_all_files()