blob: 8a7317adce17ff2d881196ce17e8260ac94dc7d0 [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
Wilson Ngd6181882014-04-14 16:28:35 -0700161 elif base_type == "of_bsn_vport_header_t":
162 # This is a special case: it has non-zero min length
163 # but is variable length
164 bytes = -1
165 len_update = 4
Rich Lane293d3bf2014-10-16 09:52:22 -0700166 elif base_type == "of_port_desc_t":
167 # This is a special case: it has non-zero min length
168 # but is variable length
169 bytes = -1
170 len_update = of_g.base_length[(base_class, wire_version)]
Andreas Wundsam76db0062013-11-15 13:34:41 -0800171 elif base_type in of_g.of_base_types:
172 bytes = of_g.of_base_types[base_type]["bytes"]
173 else:
174 print "UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type)
175 log("UNKNOWN TYPE for %s %s: %s" % (cls, name, base_type))
176 bytes = -1
177
178 # If bytes
179 if bytes > 0:
180 len_update = count * bytes
181
182 if bytes == -1:
183 return -1, len_update
184
185 return offset + (count * bytes), len_update
186
187def calculate_offsets_and_lengths(ordered_classes, classes, wire_version):
188 """
189 Generate the offsets for fixed offset class members
190 Also calculate the class_sizes when possible.
191
192 @param classes The classes to process
193 @param wire_version The wire version for this set of classes
194
195 Updates global variables
196 """
197
198 lists = set()
199
200 # Generate offsets
201 for cls in ordered_classes:
202 fixed_offset = 0 # The last "good" offset seen
203 offset = 0
204 last_offset = 0
205 last_name = "-"
206 for member in classes[cls]:
207 m_type = member["m_type"]
208 name = member["name"]
209 if last_offset == -1:
210 if name == "pad":
211 log("Skipping pad for special offset for %s" % cls)
212 else:
213 log("SPECIAL OFS: Member %s (prev %s), class %s ver %d" %
214 (name, last_name, cls, wire_version))
215 if (((cls, name) in of_g.special_offsets) and
216 (of_g.special_offsets[(cls, name)] != last_name)):
217 debug("ERROR: special offset prev name changed")
218 debug(" cls %s. name %s. version %d. was %s. now %s" %
219 cls, name, wire_version,
220 of_g.special_offsets[(cls, name)], last_name)
221 sys.exit(1)
222 of_g.special_offsets[(cls, name)] = last_name
223
224 member["offset"] = offset
225 if m_type.find("list(") == 0:
226 (list_name, base_type) = loxi_utils.list_name_extract(m_type)
227 lists.add(list_name)
228 member["m_type"] = list_name + "_t"
229 offset = -1
230 elif m_type.find("struct") == 0:
231 debug("ERROR found struct: %s.%s " % (cls, name))
232 sys.exit(1)
233 elif m_type == "octets":
234 log("offset gen skipping octets: %s.%s " % (cls, name))
235 offset = -1
236 else:
237 offset, len_update = update_offset(cls, wire_version, name,
238 offset, m_type)
239 if offset != -1:
240 fixed_offset = offset
241 else:
242 fixed_offset += len_update
243 log("offset is -1 for %s.%s version %d " %
244 (cls, name, wire_version))
245 last_offset = offset
246 last_name = name
247 of_g.base_length[(cls, wire_version)] = fixed_offset
248 if (offset != -1):
249 of_g.is_fixed_length.add((cls, wire_version))
Rich Lane2cc2b862014-06-13 14:50:17 -0700250
Andreas Wundsam76db0062013-11-15 13:34:41 -0800251 for list_type in lists:
252 classes[list_type] = []
253 of_g.ordered_classes[wire_version].append(list_type)
254 of_g.base_length[(list_type, wire_version)] = 0
255
256def order_and_assign_object_ids():
257 """
258 Order all classes and assign object ids to all classes.
259
260 This is done to promote a reasonable order of the objects, putting
261 messages first followed by non-messages. No assumptions should be
262 made about the order, nor about contiguous numbering. However, the
263 numbers should all be reasonably small allowing arrays indexed by
264 these enum values to be defined.
265 """
266
267 # Generate separate message and non-message ordered lists
268 for cls in of_g.unified:
269 if loxi_utils.class_is_message(cls):
270 of_g.ordered_messages.append(cls)
271 elif loxi_utils.class_is_list(cls):
272 of_g.ordered_list_objects.append(cls)
273 else:
274 of_g.ordered_non_messages.append(cls)
275
276 of_g.ordered_messages.sort()
277 of_g.ordered_pseudo_objects.sort()
278 of_g.ordered_non_messages.sort()
279 of_g.ordered_list_objects.sort()
280 of_g.standard_class_order.extend(of_g.ordered_messages)
281 of_g.standard_class_order.extend(of_g.ordered_non_messages)
282 of_g.standard_class_order.extend(of_g.ordered_list_objects)
283
284 # This includes pseudo classes for which most code is not generated
285 of_g.all_class_order.extend(of_g.ordered_messages)
286 of_g.all_class_order.extend(of_g.ordered_non_messages)
287 of_g.all_class_order.extend(of_g.ordered_list_objects)
288 of_g.all_class_order.extend(of_g.ordered_pseudo_objects)
289
290 # Assign object IDs
291 for cls in of_g.ordered_messages:
292 of_g.unified[cls]["object_id"] = of_g.object_id
293 of_g.object_id += 1
294 for cls in of_g.ordered_non_messages:
295 of_g.unified[cls]["object_id"] = of_g.object_id
296 of_g.object_id += 1
297 for cls in of_g.ordered_list_objects:
298 of_g.unified[cls]["object_id"] = of_g.object_id
299 of_g.object_id += 1
Andreas Wundsam76db0062013-11-15 13:34:41 -0800300
301
302def initialize_versions():
303 """
304 Create an empty datastructure for each target version.
305 """
306
307 for version in loxi_globals.OFVersions.target_versions:
308 wire_version = version.wire_version
309 version_name = of_g.of_version_wire2name[wire_version]
310 of_g.wire_ver_map[wire_version] = version_name
311 versions[version_name] = dict(
312 version_name = version_name,
313 wire_version = wire_version,
314 classes = {})
315 of_g.ordered_classes[wire_version] = []
316
317 of_g.target_version_list = [ v.wire_version for v in loxi_globals.OFVersions.target_versions ]
318
319def build_ordered_classes():
320 """
321 Read in from files given on command line and update global state
322
323 @fixme Should select versions to support from command line
324 """
325
326 for version, protocol in loxi_globals.ir.items():
327 wire_version = version.wire_version
328 # Populate global state
329 version_name = of_g.of_version_wire2name[wire_version]
330
331 for ofclass in protocol.classes:
Andreas Wundsam76db0062013-11-15 13:34:41 -0800332 of_g.ordered_classes[wire_version].append(ofclass.name)
333 legacy_members = []
334 pad_count = 0
335 for m in ofclass.members:
336 if type(m) == OFPadMember:
337 m_name = 'pad%d' % pad_count
338 if m_name == 'pad0': m_name = 'pad'
339 legacy_members.append(dict(m_type='uint8_t[%d]' % m.length,
340 name=m_name))
341 pad_count += 1
342 else:
343 # HACK the C backend does not yet support of_oxm_t
344 if m.oftype == 'of_oxm_t':
Rich Lane90020b42014-04-07 12:05:45 -0700345 m_type = 'of_oxm_header_t'
Wilson Ngd6181882014-04-14 16:28:35 -0700346 # HACK the C backend does not yet support of_bsn_vport_t
347 elif m.oftype == 'of_bsn_vport_t':
348 m_type = 'of_bsn_vport_header_t'
Andreas Wundsam76db0062013-11-15 13:34:41 -0800349 else:
350 enum = find(lambda e: e.name == m.oftype, protocol.enums)
351 if enum and "wire_type" in enum.params:
352 m_type = enum.params["wire_type"]
353 else:
354 m_type = m.oftype
355 legacy_members.append(dict(m_type=m_type, name=m.name))
356 versions[version_name]['classes'][ofclass.name] = legacy_members
357
358 for enum in protocol.enums:
359 for entry in enum.entries:
360 identifiers.add_identifier(
361 translation.loxi_name(entry.name),
362 entry.name, enum.name, entry.value, wire_version,
363 of_g.identifiers, of_g.identifiers_by_group)
364
365def populate_type_maps():
366 """
367 Use the type members in the IR to fill out the legacy type_maps.
368 """
Andreas Wundsam76db0062013-11-15 13:34:41 -0800369 type_maps.generate_maps()
370
371def analyze_input():
372 """
373 Add information computed from the input, including offsets and
374 lengths of struct members and the set of list and action_id types.
375 """
376
377 # Generate header classes for inheritance parents
378 for wire_version, ordered_classes in of_g.ordered_classes.items():
379 classes = versions[of_g.of_version_wire2name[wire_version]]['classes']
380 for cls in ordered_classes:
Rich Lane8841f352014-10-12 19:18:36 -0700381 if type_maps.class_is_inheritance_root(cls):
Andreas Wundsam76db0062013-11-15 13:34:41 -0800382 new_cls = cls + '_header'
383 of_g.ordered_classes[wire_version].append(new_cls)
384 classes[new_cls] = classes[cls]
385
Andreas Wundsam76db0062013-11-15 13:34:41 -0800386 for wire_version in of_g.wire_ver_map.keys():
387 version_name = of_g.of_version_wire2name[wire_version]
388 calculate_offsets_and_lengths(
389 of_g.ordered_classes[wire_version],
390 versions[version_name]['classes'],
391 wire_version)
392
393def unify_input():
394 """
395 Create Unified View of Objects
396 """
397
398 global versions
399
400 # Add classes to unified in wire-format order so that it is easier
401 # to generate things later
402 keys = versions.keys()
403 keys.sort(reverse=True)
404 for version in keys:
405 wire_version = versions[version]["wire_version"]
406 classes = versions[version]["classes"]
407 for cls in of_g.ordered_classes[wire_version]:
408 add_class(wire_version, cls, classes[cls])