Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 1 | //===- subzero/src/IceELFSection.h - Model of ELF sections ------*- C++ -*-===// |
| 2 | // |
| 3 | // The Subzero Code Generator |
| 4 | // |
| 5 | // This file is distributed under the University of Illinois Open Source |
| 6 | // License. See LICENSE.TXT for details. |
| 7 | // |
| 8 | //===----------------------------------------------------------------------===// |
| 9 | // |
| 10 | // Representation of ELF sections. |
| 11 | // |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #ifndef SUBZERO_SRC_ICEELFSECTION_H |
| 15 | #define SUBZERO_SRC_ICEELFSECTION_H |
| 16 | |
| 17 | #include "IceDefs.h" |
| 18 | #include "IceELFStreamer.h" |
Jan Voung | ec27073 | 2015-01-12 17:00:22 -0800 | [diff] [blame] | 19 | #include "IceFixups.h" |
| 20 | #include "IceOperand.h" |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 21 | |
| 22 | using namespace llvm::ELF; |
| 23 | |
| 24 | namespace Ice { |
| 25 | |
| 26 | class ELFStreamer; |
| 27 | class ELFStringTableSection; |
| 28 | |
| 29 | // Base representation of an ELF section. |
| 30 | class ELFSection { |
Jim Stichnoth | c6ead20 | 2015-02-24 09:30:30 -0800 | [diff] [blame] | 31 | ELFSection() = delete; |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 32 | ELFSection(const ELFSection &) = delete; |
| 33 | ELFSection &operator=(const ELFSection &) = delete; |
| 34 | |
| 35 | public: |
| 36 | // Sentinel value for a section number/index for before the final |
| 37 | // section index is actually known. The dummy NULL section will be assigned |
| 38 | // number 0, and it is referenced by the dummy 0-th symbol in the symbol |
| 39 | // table, so use max() instead of 0. |
| 40 | enum { NoSectionNumber = std::numeric_limits<SizeT>::max() }; |
| 41 | |
| 42 | // Constructs an ELF section, filling in fields that will be known |
| 43 | // once the *type* of section is decided. Other fields may be updated |
| 44 | // incrementally or only after the program is completely defined. |
| 45 | ELFSection(const IceString &Name, Elf64_Word ShType, Elf64_Xword ShFlags, |
| 46 | Elf64_Xword ShAddralign, Elf64_Xword ShEntsize) |
Jim Stichnoth | eafb56c | 2015-06-22 10:35:22 -0700 | [diff] [blame^] | 47 | : Name(Name), Header() { |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 48 | Header.sh_type = ShType; |
| 49 | Header.sh_flags = ShFlags; |
| 50 | Header.sh_addralign = ShAddralign; |
| 51 | Header.sh_entsize = ShEntsize; |
| 52 | } |
| 53 | |
| 54 | // Set the section number/index after it is finally known. |
| 55 | void setNumber(SizeT N) { |
| 56 | // Should only set the number once: from NoSectionNumber -> N. |
| 57 | assert(Number == NoSectionNumber); |
| 58 | Number = N; |
| 59 | } |
| 60 | SizeT getNumber() const { |
| 61 | assert(Number != NoSectionNumber); |
| 62 | return Number; |
| 63 | } |
| 64 | |
| 65 | void setSize(Elf64_Xword sh_size) { Header.sh_size = sh_size; } |
| 66 | SizeT getCurrentSize() const { return Header.sh_size; } |
| 67 | |
| 68 | void setNameStrIndex(Elf64_Word sh_name) { Header.sh_name = sh_name; } |
| 69 | |
Jan Voung | c9ec579 | 2015-02-05 17:31:28 -0800 | [diff] [blame] | 70 | const IceString &getName() const { return Name; } |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 71 | |
| 72 | void setLinkNum(Elf64_Word sh_link) { Header.sh_link = sh_link; } |
| 73 | |
| 74 | void setInfoNum(Elf64_Word sh_info) { Header.sh_info = sh_info; } |
| 75 | |
| 76 | void setFileOffset(Elf64_Off sh_offset) { Header.sh_offset = sh_offset; } |
| 77 | |
| 78 | Elf64_Xword getSectionAlign() const { return Header.sh_addralign; } |
| 79 | |
| 80 | // Write the section header out with the given streamer. |
| 81 | template <bool IsELF64> void writeHeader(ELFStreamer &Str); |
| 82 | |
| 83 | protected: |
Jim Stichnoth | eafb56c | 2015-06-22 10:35:22 -0700 | [diff] [blame^] | 84 | ~ELFSection() = default; |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 85 | |
| 86 | // Name of the section in convenient string form (instead of a index |
| 87 | // into the Section Header String Table, which is not known till later). |
Jan Voung | c9ec579 | 2015-02-05 17:31:28 -0800 | [diff] [blame] | 88 | const IceString Name; |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 89 | |
| 90 | // The fields of the header. May only be partially initialized, but should |
| 91 | // be fully initialized before writing. |
| 92 | Elf64_Shdr Header; |
| 93 | |
| 94 | // The number of the section after laying out sections. |
Jim Stichnoth | eafb56c | 2015-06-22 10:35:22 -0700 | [diff] [blame^] | 95 | SizeT Number = NoSectionNumber; |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 96 | }; |
| 97 | |
| 98 | // Models text/code sections. Code is written out incrementally and the |
| 99 | // size of the section is then updated incrementally. |
| 100 | class ELFTextSection : public ELFSection { |
Jim Stichnoth | c6ead20 | 2015-02-24 09:30:30 -0800 | [diff] [blame] | 101 | ELFTextSection() = delete; |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 102 | ELFTextSection(const ELFTextSection &) = delete; |
| 103 | ELFTextSection &operator=(const ELFTextSection &) = delete; |
| 104 | |
| 105 | public: |
| 106 | using ELFSection::ELFSection; |
| 107 | |
| 108 | void appendData(ELFStreamer &Str, const llvm::StringRef MoreData); |
| 109 | }; |
| 110 | |
| 111 | // Models data/rodata sections. Data is written out incrementally and the |
| 112 | // size of the section is then updated incrementally. |
| 113 | // Some rodata sections may have fixed entsize and duplicates may be mergeable. |
| 114 | class ELFDataSection : public ELFSection { |
Jim Stichnoth | c6ead20 | 2015-02-24 09:30:30 -0800 | [diff] [blame] | 115 | ELFDataSection() = delete; |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 116 | ELFDataSection(const ELFDataSection &) = delete; |
| 117 | ELFDataSection &operator=(const ELFDataSection &) = delete; |
| 118 | |
| 119 | public: |
| 120 | using ELFSection::ELFSection; |
| 121 | |
| 122 | void appendData(ELFStreamer &Str, const llvm::StringRef MoreData); |
Jan Voung | 72984d8 | 2015-01-29 14:42:38 -0800 | [diff] [blame] | 123 | |
| 124 | void appendZeros(ELFStreamer &Str, SizeT NumBytes); |
| 125 | |
| 126 | void appendRelocationOffset(ELFStreamer &Str, bool IsRela, |
| 127 | RelocOffsetT RelocOffset); |
| 128 | |
| 129 | // Pad the next section offset for writing data elements to the requested |
| 130 | // alignment. If the section is NOBITS then do not actually write out |
| 131 | // the padding and only update the section size. |
| 132 | void padToAlignment(ELFStreamer &Str, Elf64_Xword Align); |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 133 | }; |
| 134 | |
| 135 | // Model of ELF symbol table entries. Besides keeping track of the fields |
| 136 | // required for an elf symbol table entry it also tracks the number that |
| 137 | // represents the symbol's final index in the symbol table. |
| 138 | struct ELFSym { |
| 139 | Elf64_Sym Sym; |
Jan Voung | ec27073 | 2015-01-12 17:00:22 -0800 | [diff] [blame] | 140 | ELFSection *Section; |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 141 | SizeT Number; |
| 142 | |
| 143 | // Sentinel value for symbols that haven't been assigned a number yet. |
| 144 | // The dummy 0-th symbol will be assigned number 0, so don't use that. |
| 145 | enum { UnknownNumber = std::numeric_limits<SizeT>::max() }; |
| 146 | |
| 147 | void setNumber(SizeT N) { |
| 148 | assert(Number == UnknownNumber); |
| 149 | Number = N; |
| 150 | } |
| 151 | |
| 152 | SizeT getNumber() const { |
| 153 | assert(Number != UnknownNumber); |
| 154 | return Number; |
| 155 | } |
| 156 | }; |
| 157 | |
| 158 | // Models a symbol table. Symbols may be added up until updateIndices is |
| 159 | // called. At that point the indices of each symbol will be finalized. |
| 160 | class ELFSymbolTableSection : public ELFSection { |
Jim Stichnoth | c6ead20 | 2015-02-24 09:30:30 -0800 | [diff] [blame] | 161 | ELFSymbolTableSection() = delete; |
| 162 | ELFSymbolTableSection(const ELFSymbolTableSection &) = delete; |
| 163 | ELFSymbolTableSection &operator=(const ELFSymbolTableSection &) = delete; |
| 164 | |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 165 | public: |
Jan Voung | f644a4b | 2015-03-19 11:57:52 -0700 | [diff] [blame] | 166 | ELFSymbolTableSection(const IceString &Name, Elf64_Word ShType, |
| 167 | Elf64_Xword ShFlags, Elf64_Xword ShAddralign, |
| 168 | Elf64_Xword ShEntsize) |
| 169 | : ELFSection(Name, ShType, ShFlags, ShAddralign, ShEntsize), |
| 170 | NullSymbol(nullptr) {} |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 171 | |
| 172 | // Create initial entry for a symbol when it is defined. |
| 173 | // Each entry should only be defined once. |
| 174 | // We might want to allow Name to be a dummy name initially, then |
| 175 | // get updated to the real thing, since Data initializers are read |
| 176 | // before the bitcode's symbol table is read. |
| 177 | void createDefinedSym(const IceString &Name, uint8_t Type, uint8_t Binding, |
| 178 | ELFSection *Section, RelocOffsetT Offset, SizeT Size); |
| 179 | |
| 180 | // Note that a symbol table entry needs to be created for the given |
| 181 | // symbol because it is undefined. |
| 182 | void noteUndefinedSym(const IceString &Name, ELFSection *NullSection); |
| 183 | |
Jan Voung | ec27073 | 2015-01-12 17:00:22 -0800 | [diff] [blame] | 184 | const ELFSym *findSymbol(const IceString &Name) const; |
| 185 | |
Jan Voung | f644a4b | 2015-03-19 11:57:52 -0700 | [diff] [blame] | 186 | void createNullSymbol(ELFSection *NullSection); |
| 187 | const ELFSym *getNullSymbol() const { return NullSymbol; } |
| 188 | |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 189 | size_t getSectionDataSize() const { |
| 190 | return (LocalSymbols.size() + GlobalSymbols.size()) * Header.sh_entsize; |
| 191 | } |
| 192 | |
| 193 | size_t getNumLocals() const { return LocalSymbols.size(); } |
| 194 | |
| 195 | void updateIndices(const ELFStringTableSection *StrTab); |
| 196 | |
| 197 | void writeData(ELFStreamer &Str, bool IsELF64); |
| 198 | |
| 199 | private: |
Jan Voung | ec27073 | 2015-01-12 17:00:22 -0800 | [diff] [blame] | 200 | // Map from symbol name to its symbol information. |
| 201 | // This assumes symbols are unique across all sections. |
| 202 | typedef IceString SymtabKey; |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 203 | typedef std::map<SymtabKey, ELFSym> SymMap; |
| 204 | |
| 205 | template <bool IsELF64> |
| 206 | void writeSymbolMap(ELFStreamer &Str, const SymMap &Map); |
| 207 | |
Jan Voung | f644a4b | 2015-03-19 11:57:52 -0700 | [diff] [blame] | 208 | const ELFSym *NullSymbol; |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 209 | // Keep Local and Global symbols separate, since the sh_info needs to |
| 210 | // know the index of the last LOCAL. |
| 211 | SymMap LocalSymbols; |
| 212 | SymMap GlobalSymbols; |
| 213 | }; |
| 214 | |
Jan Voung | ec27073 | 2015-01-12 17:00:22 -0800 | [diff] [blame] | 215 | // Models a relocation section. |
| 216 | class ELFRelocationSection : public ELFSection { |
Jim Stichnoth | c6ead20 | 2015-02-24 09:30:30 -0800 | [diff] [blame] | 217 | ELFRelocationSection() = delete; |
Jan Voung | ec27073 | 2015-01-12 17:00:22 -0800 | [diff] [blame] | 218 | ELFRelocationSection(const ELFRelocationSection &) = delete; |
| 219 | ELFRelocationSection &operator=(const ELFRelocationSection &) = delete; |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 220 | |
| 221 | public: |
Jan Voung | f644a4b | 2015-03-19 11:57:52 -0700 | [diff] [blame] | 222 | ELFRelocationSection(const IceString &Name, Elf64_Word ShType, |
| 223 | Elf64_Xword ShFlags, Elf64_Xword ShAddralign, |
| 224 | Elf64_Xword ShEntsize) |
| 225 | : ELFSection(Name, ShType, ShFlags, ShAddralign, ShEntsize), |
| 226 | RelatedSection(nullptr) {} |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 227 | |
Jan Voung | 72984d8 | 2015-01-29 14:42:38 -0800 | [diff] [blame] | 228 | const ELFSection *getRelatedSection() const { return RelatedSection; } |
| 229 | void setRelatedSection(const ELFSection *Section) { |
| 230 | RelatedSection = Section; |
| 231 | } |
Jan Voung | ec27073 | 2015-01-12 17:00:22 -0800 | [diff] [blame] | 232 | |
| 233 | // Track additional relocations which start out relative to offset 0, |
| 234 | // but should be adjusted to be relative to BaseOff. |
| 235 | void addRelocations(RelocOffsetT BaseOff, const FixupRefList &FixupRefs); |
| 236 | |
Jan Voung | 72984d8 | 2015-01-29 14:42:38 -0800 | [diff] [blame] | 237 | // Track a single additional relocation. |
| 238 | void addRelocation(const AssemblerFixup &Fixup) { Fixups.push_back(Fixup); } |
| 239 | |
Jan Voung | 261cae3 | 2015-02-01 10:31:03 -0800 | [diff] [blame] | 240 | size_t getSectionDataSize() const; |
Jan Voung | ec27073 | 2015-01-12 17:00:22 -0800 | [diff] [blame] | 241 | |
| 242 | template <bool IsELF64> |
| 243 | void writeData(const GlobalContext &Ctx, ELFStreamer &Str, |
| 244 | const ELFSymbolTableSection *SymTab); |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 245 | |
Jan Voung | 72984d8 | 2015-01-29 14:42:38 -0800 | [diff] [blame] | 246 | bool isRela() const { return Header.sh_type == SHT_RELA; } |
| 247 | |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 248 | private: |
Jan Voung | 72984d8 | 2015-01-29 14:42:38 -0800 | [diff] [blame] | 249 | const ELFSection *RelatedSection; |
Jan Voung | ec27073 | 2015-01-12 17:00:22 -0800 | [diff] [blame] | 250 | FixupList Fixups; |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 251 | }; |
| 252 | |
| 253 | // Models a string table. The user will build the string table by |
| 254 | // adding strings incrementally. At some point, all strings should be |
| 255 | // known and doLayout() should be called. After that, no other |
| 256 | // strings may be added. However, the final offsets of the strings |
| 257 | // can be discovered and used to fill out section headers and symbol |
| 258 | // table entries. |
| 259 | class ELFStringTableSection : public ELFSection { |
Jim Stichnoth | c6ead20 | 2015-02-24 09:30:30 -0800 | [diff] [blame] | 260 | ELFStringTableSection() = delete; |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 261 | ELFStringTableSection(const ELFStringTableSection &) = delete; |
| 262 | ELFStringTableSection &operator=(const ELFStringTableSection &) = delete; |
| 263 | |
| 264 | public: |
| 265 | using ELFSection::ELFSection; |
| 266 | |
| 267 | // Add a string to the table, in preparation for final layout. |
| 268 | void add(const IceString &Str); |
| 269 | |
| 270 | // Finalizes the layout of the string table and fills in the section Data. |
| 271 | void doLayout(); |
| 272 | |
| 273 | // The first byte of the string table should be \0, so it is an |
| 274 | // invalid index. Indices start out as unknown until layout is complete. |
| 275 | enum { UnknownIndex = 0 }; |
| 276 | |
| 277 | // Grabs the final index of a string after layout. Returns UnknownIndex |
| 278 | // if the string's index is not found. |
| 279 | size_t getIndex(const IceString &Str) const; |
| 280 | |
| 281 | llvm::StringRef getSectionData() const { |
| 282 | assert(isLaidOut()); |
| 283 | return llvm::StringRef(reinterpret_cast<const char *>(StringData.data()), |
| 284 | StringData.size()); |
| 285 | } |
| 286 | |
| 287 | size_t getSectionDataSize() const { return getSectionData().size(); } |
| 288 | |
| 289 | private: |
| 290 | bool isLaidOut() const { return !StringData.empty(); } |
| 291 | |
| 292 | // Strings can share a string table entry if they share the same |
| 293 | // suffix. E.g., "pop" and "lollipop" can both use the characters |
| 294 | // in "lollipop", but "pops" cannot, and "unpop" cannot either. |
| 295 | // Though, "pop", "lollipop", and "unpop" share "pop" as the suffix, |
| 296 | // "pop" can only share the characters with one of them. |
| 297 | struct SuffixComparator { |
| 298 | bool operator()(const IceString &StrA, const IceString &StrB) const; |
| 299 | }; |
| 300 | |
| 301 | typedef std::map<IceString, size_t, SuffixComparator> StringToIndexType; |
| 302 | |
| 303 | // Track strings to their index. Index will be UnknownIndex if not |
| 304 | // yet laid out. |
| 305 | StringToIndexType StringToIndexMap; |
| 306 | |
| 307 | typedef std::vector<uint8_t> RawDataType; |
| 308 | RawDataType StringData; |
| 309 | }; |
| 310 | |
| 311 | template <bool IsELF64> void ELFSection::writeHeader(ELFStreamer &Str) { |
| 312 | Str.writeELFWord<IsELF64>(Header.sh_name); |
| 313 | Str.writeELFWord<IsELF64>(Header.sh_type); |
| 314 | Str.writeELFXword<IsELF64>(Header.sh_flags); |
| 315 | Str.writeAddrOrOffset<IsELF64>(Header.sh_addr); |
| 316 | Str.writeAddrOrOffset<IsELF64>(Header.sh_offset); |
| 317 | Str.writeELFXword<IsELF64>(Header.sh_size); |
| 318 | Str.writeELFWord<IsELF64>(Header.sh_link); |
| 319 | Str.writeELFWord<IsELF64>(Header.sh_info); |
| 320 | Str.writeELFXword<IsELF64>(Header.sh_addralign); |
| 321 | Str.writeELFXword<IsELF64>(Header.sh_entsize); |
| 322 | } |
| 323 | |
| 324 | template <bool IsELF64> |
| 325 | void ELFSymbolTableSection::writeSymbolMap(ELFStreamer &Str, |
| 326 | const SymMap &Map) { |
| 327 | // The order of the fields is different, so branch on IsELF64. |
| 328 | if (IsELF64) { |
| 329 | for (auto &KeyValue : Map) { |
| 330 | const Elf64_Sym &SymInfo = KeyValue.second.Sym; |
| 331 | Str.writeELFWord<IsELF64>(SymInfo.st_name); |
| 332 | Str.write8(SymInfo.st_info); |
| 333 | Str.write8(SymInfo.st_other); |
| 334 | Str.writeLE16(SymInfo.st_shndx); |
| 335 | Str.writeAddrOrOffset<IsELF64>(SymInfo.st_value); |
| 336 | Str.writeELFXword<IsELF64>(SymInfo.st_size); |
| 337 | } |
| 338 | } else { |
| 339 | for (auto &KeyValue : Map) { |
| 340 | const Elf64_Sym &SymInfo = KeyValue.second.Sym; |
| 341 | Str.writeELFWord<IsELF64>(SymInfo.st_name); |
| 342 | Str.writeAddrOrOffset<IsELF64>(SymInfo.st_value); |
| 343 | Str.writeELFWord<IsELF64>(SymInfo.st_size); |
| 344 | Str.write8(SymInfo.st_info); |
| 345 | Str.write8(SymInfo.st_other); |
| 346 | Str.writeLE16(SymInfo.st_shndx); |
| 347 | } |
| 348 | } |
| 349 | } |
| 350 | |
Jan Voung | ec27073 | 2015-01-12 17:00:22 -0800 | [diff] [blame] | 351 | template <bool IsELF64> |
| 352 | void ELFRelocationSection::writeData(const GlobalContext &Ctx, ELFStreamer &Str, |
| 353 | const ELFSymbolTableSection *SymTab) { |
| 354 | for (const AssemblerFixup &Fixup : Fixups) { |
Jan Voung | f644a4b | 2015-03-19 11:57:52 -0700 | [diff] [blame] | 355 | const ELFSym *Symbol; |
| 356 | if (Fixup.isNullSymbol()) |
| 357 | Symbol = SymTab->getNullSymbol(); |
| 358 | else |
| 359 | Symbol = SymTab->findSymbol(Fixup.symbol(&Ctx)); |
Jan Voung | 261cae3 | 2015-02-01 10:31:03 -0800 | [diff] [blame] | 360 | if (!Symbol) |
| 361 | llvm::report_fatal_error("Missing symbol mentioned in reloc"); |
| 362 | |
| 363 | if (IsELF64) { |
| 364 | Elf64_Rela Rela; |
| 365 | Rela.r_offset = Fixup.position(); |
| 366 | Rela.setSymbolAndType(Symbol->getNumber(), Fixup.kind()); |
| 367 | Rela.r_addend = Fixup.offset(); |
| 368 | Str.writeAddrOrOffset<IsELF64>(Rela.r_offset); |
| 369 | Str.writeELFXword<IsELF64>(Rela.r_info); |
| 370 | Str.writeELFXword<IsELF64>(Rela.r_addend); |
| 371 | } else { |
| 372 | Elf32_Rel Rel; |
| 373 | Rel.r_offset = Fixup.position(); |
| 374 | Rel.setSymbolAndType(Symbol->getNumber(), Fixup.kind()); |
| 375 | Str.writeAddrOrOffset<IsELF64>(Rel.r_offset); |
| 376 | Str.writeELFWord<IsELF64>(Rel.r_info); |
Jan Voung | ec27073 | 2015-01-12 17:00:22 -0800 | [diff] [blame] | 377 | } |
| 378 | } |
| 379 | } |
| 380 | |
Jan Voung | 08c3bcd | 2014-12-01 17:55:16 -0800 | [diff] [blame] | 381 | } // end of namespace Ice |
| 382 | |
| 383 | #endif // SUBZERO_SRC_ICEELFSECTION_H |