blob: 41d8ab2aed2c2480939f996b0306c70577d0dd9e [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
Andreas Wundsam76db0062013-11-15 13:34:41 -0800289
290
291def initialize_versions():
292 """
293 Create an empty datastructure for each target version.
294 """
295
296 for version in loxi_globals.OFVersions.target_versions:
297 wire_version = version.wire_version
298 version_name = of_g.of_version_wire2name[wire_version]
299 of_g.wire_ver_map[wire_version] = version_name
300 versions[version_name] = dict(
301 version_name = version_name,
302 wire_version = wire_version,
303 classes = {})
304 of_g.ordered_classes[wire_version] = []
305
306 of_g.target_version_list = [ v.wire_version for v in loxi_globals.OFVersions.target_versions ]
307
308def build_ordered_classes():
309 """
310 Read in from files given on command line and update global state
311
312 @fixme Should select versions to support from command line
313 """
314
315 for version, protocol in loxi_globals.ir.items():
316 wire_version = version.wire_version
317 # Populate global state
318 version_name = of_g.of_version_wire2name[wire_version]
319
320 for ofclass in protocol.classes:
Andreas Wundsam76db0062013-11-15 13:34:41 -0800321 of_g.ordered_classes[wire_version].append(ofclass.name)
322 legacy_members = []
323 pad_count = 0
324 for m in ofclass.members:
325 if type(m) == OFPadMember:
326 m_name = 'pad%d' % pad_count
327 if m_name == 'pad0': m_name = 'pad'
328 legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
329 name=m_name))
330 pad_count += 1
331 else:
332 # HACK the C backend does not yet support of_oxm_t
333 if m.oftype == 'of_oxm_t':
Rich Lane90020b42014-04-07 12:05:45 -0700334 m_type = 'of_oxm_header_t'
Andreas Wundsam76db0062013-11-15 13:34:41 -0800335 else:
336 enum = find(lambda e: e.name == m.oftype, protocol.enums)
337 if enum and "wire_type" in enum.params:
338 m_type = enum.params["wire_type"]
339 else:
340 m_type = m.oftype
341 legacy_members.append(dict(m_type=m_type, name=m.name))
342 versions[version_name]['classes'][ofclass.name] = legacy_members
343
344 for enum in protocol.enums:
345 for entry in enum.entries:
346 identifiers.add_identifier(
347 translation.loxi_name(entry.name),
348 entry.name, enum.name, entry.value, wire_version,
349 of_g.identifiers, of_g.identifiers_by_group)
350
351def populate_type_maps():
352 """
353 Use the type members in the IR to fill out the legacy type_maps.
354 """
355
356 def split_inherited_cls(cls):
357 if cls == 'of_meter_band_stats': # HACK not a subtype of of_meter_band
358 return None, None
359 for parent in sorted(type_maps.inheritance_data.keys(), reverse=True):
360 if cls.startswith(parent):
361 return (parent, cls[len(parent)+1:])
362 return None, None
363
364 def find_experimenter(parent, cls):
365 for experimenter in sorted(of_g.experimenter_name_to_id.keys(), reverse=True):
366 prefix = parent + '_' + experimenter
367 if cls.startswith(prefix) and cls != prefix:
368 return experimenter
369 return None
370
371 def find_type_value(ofclass, m_name):
372 for m in ofclass.members:
373 if isinstance(m, OFTypeMember) and m.name == m_name:
374 return m.value
375 raise KeyError("ver=%d, cls=%s, m_name=%s" % (wire_version, cls, m_name))
376
377 # Most inheritance classes: actions, instructions, etc
378 for version, protocol in loxi_globals.ir.items():
379 wire_version = version.wire_version
380 for ofclass in protocol.classes:
381 cls = ofclass.name
382 parent, subcls = split_inherited_cls(cls)
383 if not (parent and subcls):
384 continue
385 if parent == 'of_oxm':
386 type_len = find_type_value(ofclass, 'type_len')
387 oxm_class = (type_len >> 16) & 0xffff
388 if oxm_class != 0x8000:
389 # Do not include experimenter OXMs in the main table
390 val = type_maps.invalid_type
391 else:
392 val = (type_len >> 8) & 0xff
393 else:
394 val = find_type_value(ofclass, 'type')
395 type_maps.inheritance_data[parent][wire_version][subcls] = val
396
397 # Extensions (only actions for now)
398 experimenter = find_experimenter(parent, cls)
399 if parent == 'of_action' and experimenter:
400 val = find_type_value(ofclass, 'subtype')
401 type_maps.extension_action_subtype[wire_version][experimenter][cls] = val
402 if wire_version >= of_g.VERSION_1_3:
403 cls2 = parent + "_id" + cls[len(parent):]
404 type_maps.extension_action_id_subtype[wire_version][experimenter][cls2] = val
Rich Laneef7b9942013-11-18 16:29:28 -0800405 elif parent == 'of_instruction' and experimenter:
406 val = find_type_value(ofclass, 'subtype')
407 type_maps.extension_instruction_subtype[wire_version][experimenter][cls] = val
Andreas Wundsam76db0062013-11-15 13:34:41 -0800408
409 # Messages
410 for version, protocol in loxi_globals.ir.items():
411 wire_version = version.wire_version
412 for ofclass in protocol.classes:
413 cls = ofclass.name
414 # HACK (though this is what loxi_utils.class_is_message() does)
415 if not [x for x in ofclass.members if isinstance(x, OFDataMember) and x.name == 'xid']:
416 continue
417 if type_maps.class_is_virtual(cls):
418 continue
Andreas Wundsam76db0062013-11-15 13:34:41 -0800419 subcls = cls[3:]
420 val = find_type_value(ofclass, 'type')
421 if not val in type_maps.message_types[wire_version].values():
422 type_maps.message_types[wire_version][subcls] = val
423
424 # Extensions
425 experimenter = find_experimenter('of', cls)
Andreas Wundsam2c0a2d72013-11-15 15:16:36 -0800426 if experimenter and ofclass.is_subclassof("of_experimenter"):
Andreas Wundsam76db0062013-11-15 13:34:41 -0800427 val = find_type_value(ofclass, 'subtype')
428 type_maps.extension_message_subtype[wire_version][experimenter][cls] = val
429
430 type_maps.generate_maps()
431
432def analyze_input():
433 """
434 Add information computed from the input, including offsets and
435 lengths of struct members and the set of list and action_id types.
436 """
437
438 # Generate header classes for inheritance parents
439 for wire_version, ordered_classes in of_g.ordered_classes.items():
440 classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
441 for cls in ordered_classes:
442 if cls in type_maps.inheritance_map:
443 new_cls = cls + '_header'
444 of_g.ordered_classes[wire_version].append(new_cls)
445 classes[new_cls] = classes[cls]
446
Andreas Wundsam76db0062013-11-15 13:34:41 -0800447 for wire_version in of_g.wire_ver_map.keys():
448 version_name = of_g.of_version_wire2name[wire_version]
449 calculate_offsets_and_lengths(
450 of_g.ordered_classes[wire_version],
451 versions[version_name]['classes'],
452 wire_version)
453
454def unify_input():
455 """
456 Create Unified View of Objects
457 """
458
459 global versions
460
461 # Add classes to unified in wire-format order so that it is easier
462 # to generate things later
463 keys = versions.keys()
464 keys.sort(reverse=True)
465 for version in keys:
466 wire_version = versions[version]["wire_version"]
467 classes = versions[version]["classes"]
468 for cls in of_g.ordered_classes[wire_version]:
469 add_class(wire_version, cls, classes[cls])