blob: 85be353b7e017fc1c094074c2524aff87f083e08 [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
Rich Lane90020b42014-04-07 12:05:45 -0700156 elif base_type == "of_oxm_header_t":
157 # This is a special case: it has non-zero min length
158 # but is variable length
159 bytes = -1
160 len_update = 4
Andreas Wundsam76db0062013-11-15 13:34:41 -0800161 elif base_type in of_g.of_base_types:
162 bytes = of_g.of_base_types[base_type]["bytes"]
163 else:
164 print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
165 log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
166 bytes = -1
167
168 # If bytes
169 if bytes > 0:
170 len_update = count * bytes
171
172 if bytes == -1:
173 return -1, len_update
174
175 return offset + (count * bytes), len_update
176
177def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
178 """
179 Generate the offsets for fixed offset class members
180 Also calculate the class_sizes when possible.
181
182 @param classes The classes to process
183 @param wire_version The wire version for this set of classes
184
185 Updates global variables
186 """
187
188 lists = set()
189
190 # Generate offsets
191 for cls in ordered_classes:
192 fixed_offset = 0 # The last "good" offset seen
193 offset = 0
194 last_offset = 0
195 last_name = "-"
196 for member in classes[cls]:
197 m_type = member["m_type"]
198 name = member["name"]
199 if last_offset == -1:
200 if name == "pad":
201 log("Skipping pad for special offset for %s" % cls)
202 else:
203 log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
204 (name, last_name, cls, wire_version))
205 if (((cls, name) in of_g.special_offsets) and
206 (of_g.special_offsets[(cls, name)] != last_name)):
207 debug("ERROR: special offset prev name changed")
208 debug(" cls %s. name %s. version %d. was %s. now %s" %
209 cls, name, wire_version,
210 of_g.special_offsets[(cls, name)], last_name)
211 sys.exit(1)
212 of_g.special_offsets[(cls, name)] = last_name
213
214 member["offset"] = offset
215 if m_type.find("list(") == 0:
216 (list_name, base_type) = loxi_utils.list_name_extract(m_type)
217 lists.add(list_name)
218 member["m_type"] = list_name + "_t"
219 offset = -1
220 elif m_type.find("struct") == 0:
221 debug("ERROR found struct: %s.%s " % (cls, name))
222 sys.exit(1)
223 elif m_type == "octets":
224 log("offset gen skipping octets: %s.%s " % (cls, name))
225 offset = -1
226 else:
227 offset, len_update = update_offset(cls, wire_version, name,
228 offset, m_type)
229 if offset != -1:
230 fixed_offset = offset
231 else:
232 fixed_offset += len_update
233 log("offset is -1 for %s.%s version %d " %
234 (cls, name, wire_version))
235 last_offset = offset
236 last_name = name
237 of_g.base_length[(cls, wire_version)] = fixed_offset
238 if (offset != -1):
239 of_g.is_fixed_length.add((cls, wire_version))
240 for list_type in lists:
241 classes[list_type] = []
242 of_g.ordered_classes[wire_version].append(list_type)
243 of_g.base_length[(list_type, wire_version)] = 0
244
245def order_and_assign_object_ids():
246 """
247 Order all classes and assign object ids to all classes.
248
249 This is done to promote a reasonable order of the objects, putting
250 messages first followed by non-messages. No assumptions should be
251 made about the order, nor about contiguous numbering. However, the
252 numbers should all be reasonably small allowing arrays indexed by
253 these enum values to be defined.
254 """
255
256 # Generate separate message and non-message ordered lists
257 for cls in of_g.unified:
258 if loxi_utils.class_is_message(cls):
259 of_g.ordered_messages.append(cls)
260 elif loxi_utils.class_is_list(cls):
261 of_g.ordered_list_objects.append(cls)
262 else:
263 of_g.ordered_non_messages.append(cls)
264
265 of_g.ordered_messages.sort()
266 of_g.ordered_pseudo_objects.sort()
267 of_g.ordered_non_messages.sort()
268 of_g.ordered_list_objects.sort()
269 of_g.standard_class_order.extend(of_g.ordered_messages)
270 of_g.standard_class_order.extend(of_g.ordered_non_messages)
271 of_g.standard_class_order.extend(of_g.ordered_list_objects)
272
273 # This includes pseudo classes for which most code is not generated
274 of_g.all_class_order.extend(of_g.ordered_messages)
275 of_g.all_class_order.extend(of_g.ordered_non_messages)
276 of_g.all_class_order.extend(of_g.ordered_list_objects)
277 of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
278
279 # Assign object IDs
280 for cls in of_g.ordered_messages:
281 of_g.unified[cls]["object_id"] = of_g.object_id
282 of_g.object_id += 1
283 for cls in of_g.ordered_non_messages:
284 of_g.unified[cls]["object_id"] = of_g.object_id
285 of_g.object_id += 1
286 for cls in of_g.ordered_list_objects:
287 of_g.unified[cls]["object_id"] = of_g.object_id
288 of_g.object_id += 1
289 for cls in of_g.ordered_pseudo_objects:
290 of_g.unified[cls] = {}
291 of_g.unified[cls]["object_id"] = of_g.object_id
292 of_g.object_id += 1
293
294
295def initialize_versions():
296 """
297 Create an empty datastructure for each target version.
298 """
299
300 for version in loxi_globals.OFVersions.target_versions:
301 wire_version = version.wire_version
302 version_name = of_g.of_version_wire2name[wire_version]
303 of_g.wire_ver_map[wire_version] = version_name
304 versions[version_name] = dict(
305 version_name = version_name,
306 wire_version = wire_version,
307 classes = {})
308 of_g.ordered_classes[wire_version] = []
309
310 of_g.target_version_list = [ v.wire_version for v in loxi_globals.OFVersions.target_versions ]
311
312def build_ordered_classes():
313 """
314 Read in from files given on command line and update global state
315
316 @fixme Should select versions to support from command line
317 """
318
319 for version, protocol in loxi_globals.ir.items():
320 wire_version = version.wire_version
321 # Populate global state
322 version_name = of_g.of_version_wire2name[wire_version]
323
324 for ofclass in protocol.classes:
Andreas Wundsam76db0062013-11-15 13:34:41 -0800325 of_g.ordered_classes[wire_version].append(ofclass.name)
326 legacy_members = []
327 pad_count = 0
328 for m in ofclass.members:
329 if type(m) == OFPadMember:
330 m_name = 'pad%d' % pad_count
331 if m_name == 'pad0': m_name = 'pad'
332 legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
333 name=m_name))
334 pad_count += 1
335 else:
336 # HACK the C backend does not yet support of_oxm_t
337 if m.oftype == 'of_oxm_t':
Rich Lane90020b42014-04-07 12:05:45 -0700338 m_type = 'of_oxm_header_t'
Andreas Wundsam76db0062013-11-15 13:34:41 -0800339 else:
340 enum = find(lambda e: e.name == m.oftype, protocol.enums)
341 if enum and "wire_type" in enum.params:
342 m_type = enum.params["wire_type"]
343 else:
344 m_type = m.oftype
345 legacy_members.append(dict(m_type=m_type, name=m.name))
346 versions[version_name]['classes'][ofclass.name] = legacy_members
347
348 for enum in protocol.enums:
349 for entry in enum.entries:
350 identifiers.add_identifier(
351 translation.loxi_name(entry.name),
352 entry.name, enum.name, entry.value, wire_version,
353 of_g.identifiers, of_g.identifiers_by_group)
354
355def populate_type_maps():
356 """
357 Use the type members in the IR to fill out the legacy type_maps.
358 """
359
360 def split_inherited_cls(cls):
361 if cls == 'of_meter_band_stats': # HACK not a subtype of of_meter_band
362 return None, None
363 for parent in sorted(type_maps.inheritance_data.keys(), reverse=True):
364 if cls.startswith(parent):
365 return (parent, cls[len(parent)+1:])
366 return None, None
367
368 def find_experimenter(parent, cls):
369 for experimenter in sorted(of_g.experimenter_name_to_id.keys(), reverse=True):
370 prefix = parent + '_' + experimenter
371 if cls.startswith(prefix) and cls != prefix:
372 return experimenter
373 return None
374
375 def find_type_value(ofclass, m_name):
376 for m in ofclass.members:
377 if isinstance(m, OFTypeMember) and m.name == m_name:
378 return m.value
379 raise KeyError("ver=%d, cls=%s, m_name=%s" % (wire_version, cls, m_name))
380
381 # Most inheritance classes: actions, instructions, etc
382 for version, protocol in loxi_globals.ir.items():
383 wire_version = version.wire_version
384 for ofclass in protocol.classes:
385 cls = ofclass.name
386 parent, subcls = split_inherited_cls(cls)
387 if not (parent and subcls):
388 continue
389 if parent == 'of_oxm':
390 type_len = find_type_value(ofclass, 'type_len')
391 oxm_class = (type_len >> 16) & 0xffff
392 if oxm_class != 0x8000:
393 # Do not include experimenter OXMs in the main table
394 val = type_maps.invalid_type
395 else:
396 val = (type_len >> 8) & 0xff
397 else:
398 val = find_type_value(ofclass, 'type')
399 type_maps.inheritance_data[parent][wire_version][subcls] = val
400
401 # Extensions (only actions for now)
402 experimenter = find_experimenter(parent, cls)
403 if parent == 'of_action' and experimenter:
404 val = find_type_value(ofclass, 'subtype')
405 type_maps.extension_action_subtype[wire_version][experimenter][cls] = val
406 if wire_version >= of_g.VERSION_1_3:
407 cls2 = parent + "_id" + cls[len(parent):]
408 type_maps.extension_action_id_subtype[wire_version][experimenter][cls2] = val
Rich Laneef7b9942013-11-18 16:29:28 -0800409 elif parent == 'of_instruction' and experimenter:
410 val = find_type_value(ofclass, 'subtype')
411 type_maps.extension_instruction_subtype[wire_version][experimenter][cls] = val
Andreas Wundsam76db0062013-11-15 13:34:41 -0800412
413 # Messages
414 for version, protocol in loxi_globals.ir.items():
415 wire_version = version.wire_version
416 for ofclass in protocol.classes:
417 cls = ofclass.name
418 # HACK (though this is what loxi_utils.class_is_message() does)
419 if not [x for x in ofclass.members if isinstance(x, OFDataMember) and x.name == 'xid']:
420 continue
421 if type_maps.class_is_virtual(cls):
422 continue
Andreas Wundsam76db0062013-11-15 13:34:41 -0800423 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
Andreas Wundsam76db0062013-11-15 13:34:41 -0800451 for wire_version in of_g.wire_ver_map.keys():
452 version_name = of_g.of_version_wire2name[wire_version]
453 calculate_offsets_and_lengths(
454 of_g.ordered_classes[wire_version],
455 versions[version_name]['classes'],
456 wire_version)
457
458def unify_input():
459 """
460 Create Unified View of Objects
461 """
462
463 global versions
464
465 # Add classes to unified in wire-format order so that it is easier
466 # to generate things later
467 keys = versions.keys()
468 keys.sort(reverse=True)
469 for version in keys:
470 wire_version = versions[version]["wire_version"]
471 classes = versions[version]["classes"]
472 for cls in of_g.ordered_classes[wire_version]:
473 add_class(wire_version, cls, classes[cls])
474
475
476def log_all_class_info():
477 """
478 Log the results of processing the input
479
480 Debug function
481 """
482
483 for cls in of_g.unified:
484 for v in of_g.unified[cls]:
485 if type(v) == type(0):
486 log("cls: %s. ver: %d. base len %d. %s" %
487 (str(cls), v, of_g.base_length[(cls, v)],
488 loxi_utils.class_is_var_len(cls,v) and "not fixed"
489 or "fixed"))
490 if "use_version" in of_g.unified[cls][v]:
491 log("cls %s: v %d mapped to %d" % (str(cls), v,
492 of_g.unified[cls][v]["use_version"]))
493 if "members" in of_g.unified[cls][v]:
494 for member in of_g.unified[cls][v]["members"]:
495 log(" %-20s: type %-20s. offset %3d" %
496 (member["name"], member["m_type"],
497 member["offset"]))
498
499def generate_all_files():
500 """
501 Create the files for the language target
502 """
503 for (name, fn) in lang_module.targets.items():
504 path = of_g.options.install_dir + '/' + name
505 os.system("mkdir -p %s" % os.path.dirname(path))
506 with open(path, "w") as outfile:
507 fn(outfile, os.path.basename(name))
508 print("Wrote contents for " + name)
509
510if __name__ == '__main__':
511 of_g.loxigen_log_file = open("loxigen.log", "w")
512 of_g.loxigen_dbg_file = sys.stdout
513
514 of_g.process_commandline()
515 # @fixme Use command line params to select log
516
517 if not config_sanity_check():
518 debug("Config sanity check failed\n")
519 sys.exit(1)
520
521 # Import the language file
522 lang_file = "lang_%s" % of_g.options.lang
523 lang_module = __import__(lang_file)
524
525 # If list files, just list auto-gen files to stdout and exit
526 if of_g.options.list_files:
527 for name in lang_module.targets:
528 print of_g.options.install_dir + '/' + name
529 sys.exit(0)
530
531 log("\nGenerating files for target language %s\n" % of_g.options.lang)
532
533 initialize_versions()
534 read_input()
535 populate_type_maps()
536 analyze_input()
537 unify_input()
538 order_and_assign_object_ids()
539 log_all_class_info()
540 generate_all_files()