// Copyright (c) 2013, Kenton Varda <temporal@gmail.com> // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "node-translator.h" #include "parser.h" // only for generateGroupId() #include <kj/debug.h> #include <kj/arena.h> #include <set> #include <map> #include <limits> namespace capnp { namespace compiler { class NodeTranslator::StructLayout { // Massive, disgusting class which implements the layout algorithm, which decides the offset // for each field. public: template <typename UIntType> struct HoleSet { inline HoleSet(): holes{0, 0, 0, 0, 0, 0} {} // Represents a set of "holes" within a segment of allocated space, up to one hole of each // power-of-two size between 1 bit and 32 bits. // // The amount of "used" space in a struct's data segment can always be represented as a // combination of a word count and a HoleSet. The HoleSet represents the space lost to // "padding". // // There can never be more than one hole of any particular size. Why is this? Well, consider // that every data field has a power-of-two size, every field must be aligned to a multiple of // its size, and the maximum size of a single field is 64 bits. If we need to add a new field // of N bits, there are two possibilities: // 1. A hole of size N or larger exists. In this case, we find the smallest hole that is at // least N bits. Let's say that that hole has size M. We allocate the first N bits of the // hole to the new field. The remaining M - N bits become a series of holes of sizes N*2, // N*4, ..., M / 2. We know no holes of these sizes existed before because we chose M to be // the smallest available hole larger than N. So, there is still no more than one hole of // each size, and no hole larger than any hole that existed previously. // 2. No hole equal or larger N exists. In that case we extend the data section's size by one // word, creating a new 64-bit hole at the end. We then allocate N bits from it, creating // a series of holes between N and 64 bits, as described in point (1). Thus, again, there // is still at most one hole of each size, and the largest hole is 32 bits. UIntType holes[6]; // The offset of each hole as a multiple of its size. A value of zero indicates that no hole // exists. Notice that it is impossible for any actual hole to have an offset of zero, because // the first field allocated is always placed at the very beginning of the section. So either // the section has a size of zero (in which case there are no holes), or offset zero is // already allocated and therefore cannot be a hole. kj::Maybe<UIntType> tryAllocate(UIntType lgSize) { // Try to find space for a field of size lgSize^2 within the set of holes. If found, // remove it from the holes, and return its offset (as a multiple of its size). If there // is no such space, returns zero (no hole can be at offset zero, as explained above). if (lgSize >= KJ_ARRAY_SIZE(holes)) { return nullptr; } else if (holes[lgSize] != 0) { UIntType result = holes[lgSize]; holes[lgSize] = 0; return result; } else { KJ_IF_MAYBE(next, tryAllocate(lgSize + 1)) { UIntType result = *next * 2; holes[lgSize] = result + 1; return result; } else { return nullptr; } } } uint assertHoleAndAllocate(UIntType lgSize) { KJ_ASSERT(holes[lgSize] != 0); uint result = holes[lgSize]; holes[lgSize] = 0; return result; } void addHolesAtEnd(UIntType lgSize, UIntType offset, UIntType limitLgSize = KJ_ARRAY_SIZE(holes)) { // Add new holes of progressively larger sizes in the range [lgSize, limitLgSize) starting // from the given offset. The idea is that you just allocated an lgSize-sized field from // an limitLgSize-sized space, such as a newly-added word on the end of the data segment. KJ_DREQUIRE(limitLgSize <= KJ_ARRAY_SIZE(holes)); while (lgSize < limitLgSize) { KJ_DREQUIRE(holes[lgSize] == 0); KJ_DREQUIRE(offset % 2 == 1); holes[lgSize] = offset; ++lgSize; offset = (offset + 1) / 2; } } bool tryExpand(UIntType oldLgSize, uint oldOffset, uint expansionFactor) { // Try to expand the value at the given location by combining it with subsequent holes, so // as to expand the location to be 2^expansionFactor times the size that it started as. // (In other words, the new lgSize is oldLgSize + expansionFactor.) if (expansionFactor == 0) { // No expansion requested. return true; } if (holes[oldLgSize] != oldOffset + 1) { // The space immediately after the location is not a hole. return false; } // We can expand the location by one factor by combining it with a hole. Try to further // expand from there to the number of factors requested. if (tryExpand(oldLgSize + 1, oldOffset >> 1, expansionFactor - 1)) { // Success. Consume the hole. holes[oldLgSize] = 0; return true; } else { return false; } } kj::Maybe<uint> smallestAtLeast(uint size) { // Return the size of the smallest hole that is equal to or larger than the given size. for (uint i = size; i < KJ_ARRAY_SIZE(holes); i++) { if (holes[i] != 0) { return i; } } return nullptr; } uint getFirstWordUsed() { // Computes the lg of the amount of space used in the first word of the section. // If there is a 32-bit hole with a 32-bit offset, no more than the first 32 bits are used. // If no more than the first 32 bits are used, and there is a 16-bit hole with a 16-bit // offset, then no more than the first 16 bits are used. And so on. for (uint i = KJ_ARRAY_SIZE(holes); i > 0; i--) { if (holes[i - 1] != 1) { return i; } } return 0; } }; struct StructOrGroup { // Abstract interface for scopes in which fields can be added. virtual void addVoid() = 0; virtual uint addData(uint lgSize) = 0; virtual uint addPointer() = 0; virtual bool tryExpandData(uint oldLgSize, uint oldOffset, uint expansionFactor) = 0; // Try to expand the given previously-allocated space by 2^expansionFactor. Succeeds -- // returning true -- if the following space happens to be empty, making this expansion possible. // Otherwise, returns false. }; struct Top: public StructOrGroup { uint dataWordCount = 0; uint pointerCount = 0; // Size of the struct so far. HoleSet<uint> holes; void addVoid() override {} uint addData(uint lgSize) override { KJ_IF_MAYBE(hole, holes.tryAllocate(lgSize)) { return *hole; } else { uint offset = dataWordCount++ << (6 - lgSize); holes.addHolesAtEnd(lgSize, offset + 1); return offset; } } uint addPointer() override { return pointerCount++; } bool tryExpandData(uint oldLgSize, uint oldOffset, uint expansionFactor) override { return holes.tryExpand(oldLgSize, oldOffset, expansionFactor); } Top() = default; KJ_DISALLOW_COPY(Top); }; struct Union { struct DataLocation { uint lgSize; uint offset; bool tryExpandTo(Union& u, uint newLgSize) { if (newLgSize <= lgSize) { return true; } else if (u.parent.tryExpandData(lgSize, offset, newLgSize - lgSize)) { offset >>= (newLgSize - lgSize); lgSize = newLgSize; return true; } else { return false; } } }; StructOrGroup& parent; uint groupCount = 0; kj::Maybe<uint> discriminantOffset; kj::Vector<DataLocation> dataLocations; kj::Vector<uint> pointerLocations; inline Union(StructOrGroup& parent): parent(parent) {} KJ_DISALLOW_COPY(Union); uint addNewDataLocation(uint lgSize) { // Add a whole new data location to the union with the given size. uint offset = parent.addData(lgSize); dataLocations.add(DataLocation { lgSize, offset }); return offset; } uint addNewPointerLocation() { // Add a whole new pointer location to the union with the given size. return pointerLocations.add(parent.addPointer()); } void newGroupAddingFirstMember() { if (++groupCount == 2) { addDiscriminant(); } } bool addDiscriminant() { if (discriminantOffset == nullptr) { discriminantOffset = parent.addData(4); // 2^4 = 16 bits return true; } else { return false; } } }; struct Group: public StructOrGroup { public: class DataLocationUsage { public: DataLocationUsage(): isUsed(false) {} explicit DataLocationUsage(uint lgSize): isUsed(true), lgSizeUsed(lgSize) {} kj::Maybe<uint> smallestHoleAtLeast(Union::DataLocation& location, uint lgSize) { // Find the smallest single hole that is at least the given size. This is used to find the // optimal place to allocate each field -- it is placed in the smallest slot where it fits, // to reduce fragmentation. Returns the size of the hole, if found. if (!isUsed) { // The location is effectively one big hole. if (lgSize <= location.lgSize) { return location.lgSize; } else { return nullptr; } } else if (lgSize >= lgSizeUsed) { // Requested size is at least our current usage, so clearly won't fit in any current // holes, but if the location's size is larger than what we're using, we'd be able to // expand. if (lgSize < location.lgSize) { return lgSize; } else { return nullptr; } } else KJ_IF_MAYBE(result, holes.smallestAtLeast(lgSize)) { // There's a hole. return *result; } else { // The requested size is smaller than what we're already using, but there are no holes // available. If we could double our size, then we could allocate in the new space. if (lgSizeUsed < location.lgSize) { // We effectively create a new hole the same size as the current usage. return lgSizeUsed; } else { return nullptr; } } } uint allocateFromHole(Group& group, Union::DataLocation& location, uint lgSize) { // Allocate the given space from an existing hole, given smallestHoleAtLeast() already // returned non-null indicating such a hole exists. uint result; if (!isUsed) { // The location is totally unused, so just allocate from the beginning. KJ_DASSERT(lgSize <= location.lgSize, "Did smallestHoleAtLeast() really find a hole?"); result = 0; isUsed = true; lgSizeUsed = lgSize; } else if (lgSize >= lgSizeUsed) { // Requested size is at least our current usage, so clearly won't fit in any holes. // We must expand to double the requested size, and return the second half. KJ_DASSERT(lgSize < location.lgSize, "Did smallestHoleAtLeast() really find a hole?"); holes.addHolesAtEnd(lgSizeUsed, 1, lgSize); lgSizeUsed = lgSize + 1; result = 1; } else KJ_IF_MAYBE(hole, holes.tryAllocate(lgSize)) { // Found a hole. result = *hole; } else { // The requested size is smaller than what we're using so far, but didn't fit in a // hole. We should double our "used" size, then allocate from the new space. KJ_DASSERT(lgSizeUsed < location.lgSize, "Did smallestHoleAtLeast() really find a hole?"); result = 1 << (lgSizeUsed - lgSize); holes.addHolesAtEnd(lgSize, result + 1, lgSizeUsed); lgSizeUsed += 1; } // Adjust the offset according to the location's offset before returning. uint locationOffset = location.offset << (location.lgSize - lgSize); return locationOffset + result; } kj::Maybe<uint> tryAllocateByExpanding( Group& group, Union::DataLocation& location, uint lgSize) { // Attempt to allocate the given size by requesting that the parent union expand this // location to fit. This is used if smallestHoleAtLeast() already determined that there // are no holes that would fit, so we don't bother checking that. if (!isUsed) { if (location.tryExpandTo(group.parent, lgSize)) { isUsed = true; lgSizeUsed = lgSize; return location.offset << (location.lgSize - lgSize); } else { return nullptr; } } else { uint newSize = kj::max(lgSizeUsed, lgSize) + 1; if (tryExpandUsage(group, location, newSize)) { uint result = KJ_ASSERT_NONNULL(holes.tryAllocate(lgSize)); uint locationOffset = location.offset << (location.lgSize - lgSize); return locationOffset + result; } else { return nullptr; } } } bool tryExpand(Group& group, Union::DataLocation& location, uint oldLgSize, uint oldOffset, uint expansionFactor) { if (oldOffset == 0 && lgSizeUsed == oldLgSize) { // This location contains exactly the requested data, so just expand the whole thing. return tryExpandUsage(group, location, oldLgSize + expansionFactor); } else { // This location contains the requested data plus other stuff. Therefore the data cannot // possibly expand past the end of the space we've already marked used without either // overlapping with something else or breaking alignment rules. We only have to combine // it with holes. return holes.tryExpand(oldLgSize, oldOffset, expansionFactor); } } private: bool isUsed; // Whether or not this location has been used at all by the group. uint8_t lgSizeUsed; // Amount of space from the location which is "used". This is the minimum size needed to // cover all allocated space. Only meaningful if `isUsed` is true. HoleSet<uint8_t> holes; // Indicates holes present in the space designated by `lgSizeUsed`. The offsets in this // HoleSet are relative to the beginning of this particular data location, not the beginning // of the struct. bool tryExpandUsage(Group& group, Union::DataLocation& location, uint desiredUsage) { if (desiredUsage > location.lgSize) { // Need to expand the underlying slot. if (!location.tryExpandTo(group.parent, desiredUsage)) { return false; } } // Underlying slot is big enough, so expand our size and update holes. holes.addHolesAtEnd(lgSizeUsed, 1, desiredUsage); lgSizeUsed = desiredUsage; return true; } }; Union& parent; kj::Vector<DataLocationUsage> parentDataLocationUsage; // Vector corresponding to the parent union's `dataLocations`, indicating how much of each // location has already been allocated. uint parentPointerLocationUsage = 0; // Number of parent's pointer locations that have been used by this group. bool hasMembers = false; inline Group(Union& parent): parent(parent) {} KJ_DISALLOW_COPY(Group); void addVoid() override { if (!hasMembers) { hasMembers = true; parent.newGroupAddingFirstMember(); } } uint addData(uint lgSize) override { addVoid(); uint bestSize = std::numeric_limits<uint>::max(); kj::Maybe<uint> bestLocation = nullptr; for (uint i = 0; i < parent.dataLocations.size(); i++) { // If we haven't seen this DataLocation yet, add a corresponding DataLocationUsage. if (parentDataLocationUsage.size() == i) { parentDataLocationUsage.add(); } auto& usage = parentDataLocationUsage[i]; KJ_IF_MAYBE(hole, usage.smallestHoleAtLeast(parent.dataLocations[i], lgSize)) { if (*hole < bestSize) { bestSize = *hole; bestLocation = i; } } } KJ_IF_MAYBE(best, bestLocation) { return parentDataLocationUsage[*best].allocateFromHole( *this, parent.dataLocations[*best], lgSize); } // There are no holes at all in the union big enough to fit this field. Go back through all // of the locations and attempt to expand them to fit. for (uint i = 0; i < parent.dataLocations.size(); i++) { KJ_IF_MAYBE(result, parentDataLocationUsage[i].tryAllocateByExpanding( *this, parent.dataLocations[i], lgSize)) { return *result; } } // Couldn't find any space in the existing locations, so add a new one. uint result = parent.addNewDataLocation(lgSize); parentDataLocationUsage.add(lgSize); return result; } uint addPointer() override { addVoid(); if (parentPointerLocationUsage < parent.pointerLocations.size()) { return parent.pointerLocations[parentPointerLocationUsage++]; } else { parentPointerLocationUsage++; return parent.addNewPointerLocation(); } } bool tryExpandData(uint oldLgSize, uint oldOffset, uint expansionFactor) override { if (oldLgSize + expansionFactor > 6 || (oldOffset & ((1 << expansionFactor) - 1)) != 0) { // Expansion is not possible because the new size is too large or the offset is not // properly-aligned. } for (uint i = 0; i < parentDataLocationUsage.size(); i++) { auto& location = parent.dataLocations[i]; if (location.lgSize >= oldLgSize && oldOffset >> (location.lgSize - oldLgSize) == location.offset) { // The location we're trying to expand is a subset of this data location. auto& usage = parentDataLocationUsage[i]; // Adjust the offset to be only within this location. uint localOldOffset = oldOffset - (location.offset << (location.lgSize - oldLgSize)); // Try to expand. return usage.tryExpand(*this, location, oldLgSize, localOldOffset, expansionFactor); } } KJ_FAIL_ASSERT("Tried to expand field that was never allocated."); return false; } }; Top& getTop() { return top; } private: Top top; }; // ======================================================================================= NodeTranslator::NodeTranslator( const Resolver& resolver, const ErrorReporter& errorReporter, const Declaration::Reader& decl, Orphan<schema::Node> wipNodeParam, bool compileAnnotations) : resolver(resolver), errorReporter(errorReporter), compileAnnotations(compileAnnotations), wipNode(kj::mv(wipNodeParam)) { compileNode(decl, wipNode.get()); } NodeTranslator::NodeSet NodeTranslator::getBootstrapNode() { return NodeSet { wipNode.getReader(), KJ_MAP(g, groups) { return g.getReader(); } }; } NodeTranslator::NodeSet NodeTranslator::finish() { // Careful about iteration here: compileFinalValue() may actually add more elements to // `unfinishedValues`, invalidating iterators in the process. for (size_t i = 0; i < unfinishedValues.size(); i++) { auto& value = unfinishedValues[i]; compileValue(value.source, value.type, value.target, false); } return getBootstrapNode(); } class NodeTranslator::DuplicateNameDetector { public: inline explicit DuplicateNameDetector(const ErrorReporter& errorReporter) : errorReporter(errorReporter) {} void check(List<Declaration>::Reader nestedDecls, Declaration::Which parentKind); private: const ErrorReporter& errorReporter; std::map<kj::StringPtr, LocatedText::Reader> names; }; void NodeTranslator::compileNode(Declaration::Reader decl, schema::Node::Builder builder) { DuplicateNameDetector(errorReporter) .check(decl.getNestedDecls(), decl.which()); kj::StringPtr targetsFlagName; switch (decl.which()) { case Declaration::FILE: targetsFlagName = "targetsFile"; break; case Declaration::CONST: compileConst(decl.getConst(), builder.initConst()); targetsFlagName = "targetsConst"; break; case Declaration::ANNOTATION: compileAnnotation(decl.getAnnotation(), builder.initAnnotation()); targetsFlagName = "targetsAnnotation"; break; case Declaration::ENUM: compileEnum(decl.getEnum(), decl.getNestedDecls(), builder); targetsFlagName = "targetsEnum"; break; case Declaration::STRUCT: compileStruct(decl.getStruct(), decl.getNestedDecls(), builder); targetsFlagName = "targetsStruct"; break; case Declaration::INTERFACE: compileInterface(decl.getInterface(), decl.getNestedDecls(), builder); targetsFlagName = "targetsInterface"; break; default: KJ_FAIL_REQUIRE("This Declaration is not a node."); break; } builder.adoptAnnotations(compileAnnotationApplications(decl.getAnnotations(), targetsFlagName)); } void NodeTranslator::DuplicateNameDetector::check( List<Declaration>::Reader nestedDecls, Declaration::Which parentKind) { for (auto decl: nestedDecls) { { auto name = decl.getName(); auto nameText = name.getValue(); auto insertResult = names.insert(std::make_pair(nameText, name)); if (!insertResult.second) { if (nameText.size() == 0 && decl.isUnion()) { errorReporter.addErrorOn( name, kj::str("An unnamed union is already defined in this scope.")); errorReporter.addErrorOn( insertResult.first->second, kj::str("Previously defined here.")); } else { errorReporter.addErrorOn( name, kj::str("'", nameText, "' is already defined in this scope.")); errorReporter.addErrorOn( insertResult.first->second, kj::str("'", nameText, "' previously defined here.")); } } } switch (decl.which()) { case Declaration::USING: case Declaration::CONST: case Declaration::ENUM: case Declaration::STRUCT: case Declaration::INTERFACE: case Declaration::ANNOTATION: switch (parentKind) { case Declaration::FILE: case Declaration::STRUCT: case Declaration::INTERFACE: // OK. break; default: errorReporter.addErrorOn(decl, "This kind of declaration doesn't belong here."); break; } break; case Declaration::ENUMERANT: if (parentKind != Declaration::ENUM) { errorReporter.addErrorOn(decl, "Enumerants can only appear in enums."); } break; case Declaration::METHOD: if (parentKind != Declaration::INTERFACE) { errorReporter.addErrorOn(decl, "Methods can only appear in interfaces."); } break; case Declaration::FIELD: case Declaration::UNION: case Declaration::GROUP: switch (parentKind) { case Declaration::STRUCT: case Declaration::UNION: case Declaration::GROUP: // OK. break; default: errorReporter.addErrorOn(decl, "This declaration can only appear in structs."); break; } // Struct members may have nested decls. We need to check those here, because no one else // is going to do it. if (decl.getName().getValue().size() == 0) { // Unnamed union. Check members as if they are in the same scope. check(decl.getNestedDecls(), decl.which()); } else { // Children are in their own scope. DuplicateNameDetector(errorReporter) .check(decl.getNestedDecls(), decl.which()); } break; default: errorReporter.addErrorOn(decl, "This kind of declaration doesn't belong here."); break; } } } void NodeTranslator::disallowNested(List<Declaration>::Reader nestedDecls) { for (auto decl: nestedDecls) { errorReporter.addErrorOn(decl, "Nested declaration not allowed here."); } } void NodeTranslator::compileConst(Declaration::Const::Reader decl, schema::Node::Const::Builder builder) { auto typeBuilder = builder.initType(); if (compileType(decl.getType(), typeBuilder)) { compileBootstrapValue(decl.getValue(), typeBuilder.asReader(), builder.initValue()); } } void NodeTranslator::compileAnnotation(Declaration::Annotation::Reader decl, schema::Node::Annotation::Builder builder) { compileType(decl.getType(), builder.initType()); // Dynamically copy over the values of all of the "targets" members. DynamicStruct::Reader src = decl; DynamicStruct::Builder dst = builder; for (auto srcField: src.getSchema().getFields()) { kj::StringPtr fieldName = srcField.getProto().getName(); if (fieldName.startsWith("targets")) { auto dstField = dst.getSchema().getFieldByName(fieldName); dst.set(dstField, src.get(srcField)); } } } class NodeTranslator::DuplicateOrdinalDetector { public: DuplicateOrdinalDetector(const ErrorReporter& errorReporter): errorReporter(errorReporter) {} void check(LocatedInteger::Reader ordinal) { if (ordinal.getValue() < expectedOrdinal) { errorReporter.addErrorOn(ordinal, "Duplicate ordinal number."); KJ_IF_MAYBE(last, lastOrdinalLocation) { errorReporter.addErrorOn( *last, kj::str("Ordinal @", last->getValue(), " originally used here.")); // Don't report original again. lastOrdinalLocation = nullptr; } } else if (ordinal.getValue() > expectedOrdinal) { errorReporter.addErrorOn(ordinal, kj::str("Skipped ordinal @", expectedOrdinal, ". Ordinals must be sequential with no " "holes.")); expectedOrdinal = ordinal.getValue() + 1; } else { ++expectedOrdinal; lastOrdinalLocation = ordinal; } } private: const ErrorReporter& errorReporter; uint expectedOrdinal = 0; kj::Maybe<LocatedInteger::Reader> lastOrdinalLocation; }; void NodeTranslator::compileEnum(Void decl, List<Declaration>::Reader members, schema::Node::Builder builder) { // maps ordinal -> (code order, declaration) std::multimap<uint, std::pair<uint, Declaration::Reader>> enumerants; uint codeOrder = 0; for (auto member: members) { if (member.isEnumerant()) { enumerants.insert( std::make_pair(member.getId().getOrdinal().getValue(), std::make_pair(codeOrder++, member))); } } auto list = builder.initEnum().initEnumerants(enumerants.size()); uint i = 0; DuplicateOrdinalDetector dupDetector(errorReporter); for (auto& entry: enumerants) { uint codeOrder = entry.second.first; Declaration::Reader enumerantDecl = entry.second.second; dupDetector.check(enumerantDecl.getId().getOrdinal()); auto enumerantBuilder = list[i++]; enumerantBuilder.setName(enumerantDecl.getName().getValue()); enumerantBuilder.setCodeOrder(codeOrder); enumerantBuilder.adoptAnnotations(compileAnnotationApplications( enumerantDecl.getAnnotations(), "targetsEnumerant")); } } // ------------------------------------------------------------------- class NodeTranslator::StructTranslator { public: explicit StructTranslator(NodeTranslator& translator) : translator(translator), errorReporter(translator.errorReporter) {} KJ_DISALLOW_COPY(StructTranslator); void translate(Void decl, List<Declaration>::Reader members, schema::Node::Builder builder) { auto structBuilder = builder.initStruct(); // Build the member-info-by-ordinal map. MemberInfo root(builder); traverseTopOrGroup(members, root, layout.getTop()); // Go through each member in ordinal order, building each member schema. DuplicateOrdinalDetector dupDetector(errorReporter); for (auto& entry: membersByOrdinal) { MemberInfo& member = *entry.second; if (member.decl.getId().isOrdinal()) { dupDetector.check(member.decl.getId().getOrdinal()); } schema::Field::Builder fieldBuilder = member.getSchema(); fieldBuilder.getOrdinal().setExplicit(entry.first); switch (member.decl.which()) { case Declaration::FIELD: { auto fieldReader = member.decl.getField(); auto nonGroup = fieldBuilder.initNonGroup(); auto typeBuilder = nonGroup.initType(); if (translator.compileType(fieldReader.getType(), typeBuilder)) { switch (fieldReader.getDefaultValue().which()) { case Declaration::Field::DefaultValue::VALUE: translator.compileBootstrapValue(fieldReader.getDefaultValue().getValue(), typeBuilder, nonGroup.initDefaultValue()); break; case Declaration::Field::DefaultValue::NONE: translator.compileDefaultDefaultValue(typeBuilder, nonGroup.initDefaultValue()); break; } } else { translator.compileDefaultDefaultValue(typeBuilder, nonGroup.initDefaultValue()); } int lgSize = -1; switch (typeBuilder.which()) { case schema::Type::VOID: lgSize = -1; break; case schema::Type::BOOL: lgSize = 0; break; case schema::Type::INT8: lgSize = 3; break; case schema::Type::INT16: lgSize = 4; break; case schema::Type::INT32: lgSize = 5; break; case schema::Type::INT64: lgSize = 6; break; case schema::Type::UINT8: lgSize = 3; break; case schema::Type::UINT16: lgSize = 4; break; case schema::Type::UINT32: lgSize = 5; break; case schema::Type::UINT64: lgSize = 6; break; case schema::Type::FLOAT32: lgSize = 5; break; case schema::Type::FLOAT64: lgSize = 6; break; case schema::Type::TEXT: lgSize = -2; break; case schema::Type::DATA: lgSize = -2; break; case schema::Type::LIST: lgSize = -2; break; case schema::Type::ENUM: lgSize = 4; break; case schema::Type::STRUCT: lgSize = -2; break; case schema::Type::INTERFACE: lgSize = -2; break; case schema::Type::OBJECT: lgSize = -2; break; } if (lgSize == -2) { // pointer nonGroup.setOffset(member.fieldScope->addPointer()); } else if (lgSize == -1) { // void member.fieldScope->addVoid(); nonGroup.setOffset(0); } else { nonGroup.setOffset(member.fieldScope->addData(lgSize)); } break; } case Declaration::UNION: if (!member.unionScope->addDiscriminant()) { errorReporter.addErrorOn(member.decl.getId().getOrdinal(), "Union ordinal, if specified, must be greater than no more than one of its " "member ordinals (i.e. there can only be one field retroactively unionized)."); } break; case Declaration::GROUP: KJ_FAIL_ASSERT("Groups don't have ordinals."); break; default: KJ_FAIL_ASSERT("Unexpected member type."); break; } } // OK, we should have built all the members. Now go through and make sure the discriminant // offsets have been copied over to the schemas and annotations have been applied. root.finishGroup(); for (auto member: allMembers) { kj::StringPtr targetsFlagName; switch (member->decl.which()) { case Declaration::FIELD: targetsFlagName = "targetsField"; break; case Declaration::UNION: member->finishGroup(); targetsFlagName = "targetsUnion"; break; case Declaration::GROUP: member->finishGroup(); targetsFlagName = "targetsGroup"; break; default: KJ_FAIL_ASSERT("Unexpected member type."); break; } builder.adoptAnnotations(translator.compileAnnotationApplications( member->decl.getAnnotations(), targetsFlagName)); } // And fill in the sizes. structBuilder.setDataWordCount(layout.getTop().dataWordCount); structBuilder.setPointerCount(layout.getTop().pointerCount); structBuilder.setPreferredListEncoding(schema::ElementSize::INLINE_COMPOSITE); if (layout.getTop().pointerCount == 0) { if (layout.getTop().dataWordCount == 0) { structBuilder.setPreferredListEncoding(schema::ElementSize::EMPTY); } else if (layout.getTop().dataWordCount == 1) { switch (layout.getTop().holes.getFirstWordUsed()) { case 0: structBuilder.setPreferredListEncoding(schema::ElementSize::BIT); break; case 1: case 2: case 3: structBuilder.setPreferredListEncoding(schema::ElementSize::BYTE); break; case 4: structBuilder.setPreferredListEncoding(schema::ElementSize::TWO_BYTES); break; case 5: structBuilder.setPreferredListEncoding(schema::ElementSize::FOUR_BYTES); break; case 6: structBuilder.setPreferredListEncoding(schema::ElementSize::EIGHT_BYTES); break; default: KJ_FAIL_ASSERT("Expected 0, 1, 2, 3, 4, 5, or 6."); break; } } } else if (layout.getTop().pointerCount == 1 && layout.getTop().dataWordCount == 0) { structBuilder.setPreferredListEncoding(schema::ElementSize::POINTER); } for (auto& group: translator.groups) { auto groupBuilder = group.get().getStruct(); groupBuilder.setDataWordCount(structBuilder.getDataWordCount()); groupBuilder.setPointerCount(structBuilder.getPointerCount()); groupBuilder.setPreferredListEncoding(structBuilder.getPreferredListEncoding()); } } private: NodeTranslator& translator; const ErrorReporter& errorReporter; StructLayout layout; kj::Arena arena; struct MemberInfo { MemberInfo* parent; // The MemberInfo for the parent scope. uint codeOrder; // Code order within the parent. uint index = 0; // Index within the parent. uint childCount = 0; // Number of children this member has. uint childInitializedCount = 0; // Number of children whose `schema` member has been initialized. This initialization happens // while walking the fields in ordinal order. uint unionDiscriminantCount = 0; // Number of children who are members of the scope's union and have had their discriminant // value decided. bool isInUnion; // Whether or not this field is in the parent's union. Declaration::Reader decl; kj::Maybe<schema::Field::Builder> schema; // Schema for the field. Initialized when getSchema() is first called. schema::Node::Builder node; // If it's a group, or the top-level struct. union { StructLayout::StructOrGroup* fieldScope; // If this member is a field, the scope of that field. This will be used to assign an // offset for the field when going through in ordinal order. StructLayout::Union* unionScope; // If this member is a union, or it is a group or top-level struct containing an unnamed // union, this is the union. This will be used to assign a discriminant offset when the // union's ordinal comes up (if the union has an explicit ordinal), as well as to finally // copy over the discriminant offset to the schema. }; inline explicit MemberInfo(schema::Node::Builder node) : parent(nullptr), codeOrder(0), isInUnion(false), node(node), unionScope(nullptr) {} inline MemberInfo(MemberInfo& parent, uint codeOrder, const Declaration::Reader& decl, StructLayout::StructOrGroup& fieldScope, bool isInUnion) : parent(&parent), codeOrder(codeOrder), isInUnion(isInUnion), decl(decl), fieldScope(&fieldScope) {} inline MemberInfo(MemberInfo& parent, uint codeOrder, const Declaration::Reader& decl, schema::Node::Builder node, bool isInUnion) : parent(&parent), codeOrder(codeOrder), isInUnion(isInUnion), decl(decl), node(node), unionScope(nullptr) {} schema::Field::Builder getSchema() { KJ_IF_MAYBE(result, schema) { return *result; } else { index = parent->childInitializedCount; auto builder = parent->addMemberSchema(); if (isInUnion) { builder.setDiscriminantValue(parent->unionDiscriminantCount++); } builder.setName(decl.getName().getValue()); builder.setCodeOrder(codeOrder); schema = builder; return builder; } } schema::Field::Builder addMemberSchema() { // Get the schema builder for the child member at the given index. This lazily/dynamically // builds the builder tree. KJ_REQUIRE(childInitializedCount < childCount); auto structNode = node.getStruct(); if (!structNode.hasFields()) { if (parent != nullptr) { getSchema(); // Make sure field exists in parent once the first child is added. } return structNode.initFields(childCount)[childInitializedCount++]; } else { return structNode.getFields()[childInitializedCount++]; } } void finishGroup() { if (unionScope != nullptr) { unionScope->addDiscriminant(); // if it hasn't happened already auto structNode = node.getStruct(); structNode.setDiscriminantCount(unionDiscriminantCount); structNode.setDiscriminantOffset(KJ_ASSERT_NONNULL(unionScope->discriminantOffset)); } if (parent != nullptr) { uint64_t groupId = generateGroupId(parent->node.getId(), index); node.setId(groupId); node.setScopeId(parent->node.getId()); getSchema().setGroup(groupId); } } }; std::multimap<uint, MemberInfo*> membersByOrdinal; // Every member that has an explicit ordinal goes into this map. We then iterate over the map // to assign field offsets (or discriminant offsets for unions). kj::Vector<MemberInfo*> allMembers; // All members, including ones that don't have ordinals. void traverseUnion(List<Declaration>::Reader members, MemberInfo& parent, StructLayout::Union& layout, uint& codeOrder) { if (members.size() < 2) { errorReporter.addErrorOn(parent.decl, "Union must have at least two members."); } for (auto member: members) { kj::Maybe<uint> ordinal; MemberInfo* memberInfo = nullptr; switch (member.which()) { case Declaration::FIELD: { parent.childCount++; // For layout purposes, pretend this field is enclosed in a one-member group. StructLayout::Group& singletonGroup = arena.allocate<StructLayout::Group>(layout); memberInfo = &arena.allocate<MemberInfo>(parent, codeOrder++, member, singletonGroup, true); allMembers.add(memberInfo); ordinal = member.getId().getOrdinal().getValue(); break; } case Declaration::UNION: errorReporter.addErrorOn(member, "Unions cannot contain unions."); break; case Declaration::GROUP: { parent.childCount++; StructLayout::Group& group = arena.allocate<StructLayout::Group>(layout); memberInfo = &arena.allocate<MemberInfo>( parent, codeOrder++, member, newGroupNode(parent.node, member.getName().getValue()), true); allMembers.add(memberInfo); traverseGroup(member.getNestedDecls(), *memberInfo, group); break; } default: // Ignore others. break; } KJ_IF_MAYBE(o, ordinal) { membersByOrdinal.insert(std::make_pair(*o, memberInfo)); } } } void traverseGroup(List<Declaration>::Reader members, MemberInfo& parent, StructLayout::StructOrGroup& layout) { if (members.size() < 1) { errorReporter.addErrorOn(parent.decl, "Group must have at least one member."); } traverseTopOrGroup(members, parent, layout); } void traverseTopOrGroup(List<Declaration>::Reader members, MemberInfo& parent, StructLayout::StructOrGroup& layout) { uint codeOrder = 0; for (auto member: members) { kj::Maybe<uint> ordinal; MemberInfo* memberInfo = nullptr; switch (member.which()) { case Declaration::FIELD: { parent.childCount++; memberInfo = &arena.allocate<MemberInfo>( parent, codeOrder++, member, layout, false); allMembers.add(memberInfo); ordinal = member.getId().getOrdinal().getValue(); break; } case Declaration::UNION: { StructLayout::Union& unionLayout = arena.allocate<StructLayout::Union>(layout); uint independentSubCodeOrder = 0; uint* subCodeOrder = &independentSubCodeOrder; if (member.getName().getValue() == "") { memberInfo = &parent; subCodeOrder = &codeOrder; } else { parent.childCount++; memberInfo = &arena.allocate<MemberInfo>( parent, codeOrder++, member, newGroupNode(parent.node, member.getName().getValue()), false); allMembers.add(memberInfo); } memberInfo->unionScope = &unionLayout; traverseUnion(member.getNestedDecls(), *memberInfo, unionLayout, *subCodeOrder); if (member.getId().isOrdinal()) { ordinal = member.getId().getOrdinal().getValue(); } break; } case Declaration::GROUP: parent.childCount++; memberInfo = &arena.allocate<MemberInfo>( parent, codeOrder++, member, newGroupNode(parent.node, member.getName().getValue()), false); allMembers.add(memberInfo); // Members of the group are laid out just like they were members of the parent, so we // just pass along the parent layout. traverseGroup(member.getNestedDecls(), *memberInfo, layout); // No ordinal for groups. break; default: // Ignore others. break; } KJ_IF_MAYBE(o, ordinal) { membersByOrdinal.insert(std::make_pair(*o, memberInfo)); } } } schema::Node::Builder newGroupNode(schema::Node::Reader parent, kj::StringPtr name) { auto orphan = Orphanage::getForMessageContaining(translator.wipNode.get()) .newOrphan<schema::Node>(); auto node = orphan.get(); // We'll set the ID and scope ID later. node.setDisplayName(kj::str(parent.getDisplayName(), '.', name)); node.setDisplayNamePrefixLength(node.getDisplayName().size() - name.size()); node.initStruct().setIsGroup(true); // The remaining contents of node.struct will be filled in later. translator.groups.add(kj::mv(orphan)); return node; } }; void NodeTranslator::compileStruct(Void decl, List<Declaration>::Reader members, schema::Node::Builder builder) { StructTranslator(*this).translate(decl, members, builder); } // ------------------------------------------------------------------- void NodeTranslator::compileInterface(Void decl, List<Declaration>::Reader members, schema::Node::Builder builder) { KJ_FAIL_ASSERT("TODO: compile interfaces"); } // ------------------------------------------------------------------- static kj::String declNameString(DeclName::Reader name) { kj::String prefix; switch (name.getBase().which()) { case DeclName::Base::RELATIVE_NAME: prefix = kj::str(name.getBase().getRelativeName()); break; case DeclName::Base::ABSOLUTE_NAME: prefix = kj::str(".", name.getBase().getAbsoluteName()); break; case DeclName::Base::IMPORT_NAME: prefix = kj::str("import \"", name.getBase().getImportName(), "\""); break; } if (name.getMemberPath().size() == 0) { return prefix; } else { auto path = name.getMemberPath(); KJ_STACK_ARRAY(kj::StringPtr, parts, path.size(), 16, 16); for (size_t i = 0; i < parts.size(); i++) { parts[i] = path[i].getValue(); } return kj::str(prefix, ".", kj::strArray(parts, ".")); } } bool NodeTranslator::compileType(TypeExpression::Reader source, schema::Type::Builder target) { auto name = source.getName(); KJ_IF_MAYBE(base, resolver.resolve(name)) { bool handledParams = false; switch (base->kind) { case Declaration::ENUM: target.setEnum(base->id); break; case Declaration::STRUCT: target.setStruct(base->id); break; case Declaration::INTERFACE: target.setInterface(base->id); break; case Declaration::BUILTIN_LIST: { auto params = source.getParams(); if (params.size() != 1) { errorReporter.addErrorOn(source, "'List' requires exactly one parameter."); return false; } auto elementType = target.initList(); if (!compileType(params[0], elementType)) { return false; } if (elementType.isObject()) { errorReporter.addErrorOn(source, "'List(Object)' is not supported."); // Seeing List(Object) later can mess things up, so change the type to Void. elementType.setVoid(); return false; } handledParams = true; break; } case Declaration::BUILTIN_VOID: target.setVoid(); break; case Declaration::BUILTIN_BOOL: target.setBool(); break; case Declaration::BUILTIN_INT8: target.setInt8(); break; case Declaration::BUILTIN_INT16: target.setInt16(); break; case Declaration::BUILTIN_INT32: target.setInt32(); break; case Declaration::BUILTIN_INT64: target.setInt64(); break; case Declaration::BUILTIN_U_INT8: target.setUint8(); break; case Declaration::BUILTIN_U_INT16: target.setUint16(); break; case Declaration::BUILTIN_U_INT32: target.setUint32(); break; case Declaration::BUILTIN_U_INT64: target.setUint64(); break; case Declaration::BUILTIN_FLOAT32: target.setFloat32(); break; case Declaration::BUILTIN_FLOAT64: target.setFloat64(); break; case Declaration::BUILTIN_TEXT: target.setText(); break; case Declaration::BUILTIN_DATA: target.setData(); break; case Declaration::BUILTIN_OBJECT: target.setObject(); break; default: errorReporter.addErrorOn(source, kj::str("'", declNameString(name), "' is not a type.")); return false; } if (!handledParams) { if (source.getParams().size() != 0) { errorReporter.addErrorOn(source, kj::str( "'", declNameString(name), "' does not accept parameters.")); return false; } } return true; } else { target.setVoid(); return false; } } // ------------------------------------------------------------------- void NodeTranslator::compileDefaultDefaultValue( schema::Type::Reader type, schema::Value::Builder target) { switch (type.which()) { case schema::Type::VOID: target.setVoid(); break; case schema::Type::BOOL: target.setBool(false); break; case schema::Type::INT8: target.setInt8(0); break; case schema::Type::INT16: target.setInt16(0); break; case schema::Type::INT32: target.setInt32(0); break; case schema::Type::INT64: target.setInt64(0); break; case schema::Type::UINT8: target.setUint8(0); break; case schema::Type::UINT16: target.setUint16(0); break; case schema::Type::UINT32: target.setUint32(0); break; case schema::Type::UINT64: target.setUint64(0); break; case schema::Type::FLOAT32: target.setFloat32(0); break; case schema::Type::FLOAT64: target.setFloat64(0); break; case schema::Type::ENUM: target.setEnum(0); break; case schema::Type::INTERFACE: target.setInterface(); break; // Bit of a hack: For "Object" types, we adopt a null orphan, which sets the field to null. // TODO(cleanup): Create a cleaner way to do this. case schema::Type::TEXT: target.adoptText(Orphan<Text>()); break; case schema::Type::DATA: target.adoptData(Orphan<Data>()); break; case schema::Type::STRUCT: target.adoptStruct(Orphan<Data>()); break; case schema::Type::LIST: target.adoptList(Orphan<Data>()); break; case schema::Type::OBJECT: target.adoptObject(Orphan<Data>()); break; } } class NodeTranslator::DynamicSlot { // Acts like a pointer to a field or list element. The target's value can be set or initialized. // This is useful when recursively compiling values. // // TODO(someday): The Dynamic API should support something like this directly. public: DynamicSlot(DynamicStruct::Builder structBuilder, StructSchema::Field field) : type(FIELD), struct_{structBuilder, field} {} DynamicSlot(DynamicList::Builder listBuilder, uint index) : type(ELEMENT), list{listBuilder, index} {} DynamicSlot(DynamicStruct::Builder structBuilder, StructSchema::Field field, StructSchema structFieldSchema) : type(STRUCT_OBJECT_FIELD), struct_{structBuilder, field}, structFieldSchema(structFieldSchema) {} DynamicSlot(DynamicStruct::Builder structBuilder, StructSchema::Field field, ListSchema listFieldSchema) : type(LIST_OBJECT_FIELD), struct_{structBuilder, field}, listFieldSchema(listFieldSchema) {} DynamicSlot(DynamicStruct::Builder structBuilder, StructSchema::Field field, EnumSchema enumFieldSchema) : type(RAW_ENUM_FIELD), struct_{structBuilder, field}, enumFieldSchema(enumFieldSchema) {} DynamicStruct::Builder initStruct() { switch (type) { case FIELD: return struct_.builder.init(struct_.field).as<DynamicStruct>(); case ELEMENT: return list.builder[list.index].as<DynamicStruct>(); case STRUCT_OBJECT_FIELD: return struct_.builder.initObject(struct_.field, structFieldSchema); case LIST_OBJECT_FIELD: KJ_FAIL_REQUIRE("Type mismatch."); case RAW_ENUM_FIELD: KJ_FAIL_REQUIRE("Type mismatch."); } KJ_FAIL_ASSERT("can't get here"); } DynamicList::Builder initList(uint size) { switch (type) { case FIELD: return struct_.builder.init(struct_.field, size).as<DynamicList>(); case ELEMENT: return list.builder.init(list.index, size).as<DynamicList>(); case STRUCT_OBJECT_FIELD: KJ_FAIL_REQUIRE("Type mismatch."); case LIST_OBJECT_FIELD: return struct_.builder.initObject(struct_.field, listFieldSchema, size); case RAW_ENUM_FIELD: KJ_FAIL_REQUIRE("Type mismatch."); } KJ_FAIL_ASSERT("can't get here"); } void set(DynamicValue::Reader value) { switch (type) { case FIELD: struct_.builder.set(struct_.field, value); return; case ELEMENT: list.builder.set(list.index, value); return; case STRUCT_OBJECT_FIELD: struct_.builder.set(struct_.field, value); return; case LIST_OBJECT_FIELD: struct_.builder.set(struct_.field, value); return; case RAW_ENUM_FIELD: struct_.builder.set(struct_.field, value.as<DynamicEnum>().getRaw()); return; } KJ_FAIL_ASSERT("can't get here"); } kj::Maybe<uint64_t> getEnumType() { // If the slot type is an enum, get its type ID. Otherwise return nullptr. // // This is really ugly. switch (type) { case FIELD: return enumIdForField(struct_.field); case ELEMENT: { if (list.builder.getSchema().whichElementType() == schema::Type::ENUM) { return list.builder.getSchema().getEnumElementType().getProto().getId(); } return nullptr; } case STRUCT_OBJECT_FIELD: return nullptr; case LIST_OBJECT_FIELD: return nullptr; case RAW_ENUM_FIELD: return enumFieldSchema.getProto().getId(); } KJ_FAIL_ASSERT("can't get here"); } private: enum Type { FIELD, ELEMENT, STRUCT_OBJECT_FIELD, LIST_OBJECT_FIELD, RAW_ENUM_FIELD }; Type type; union { struct { DynamicStruct::Builder builder; StructSchema::Field field; } struct_; struct { DynamicList::Builder builder; uint index; } list; }; union { StructSchema structFieldSchema; ListSchema listFieldSchema; EnumSchema enumFieldSchema; }; static kj::Maybe<uint64_t> enumIdForField(StructSchema::Field field) { schema::Field::Reader proto = field.getProto(); if (proto.isNonGroup()) { auto type = proto.getNonGroup().getType(); if (type.isEnum()) { return type.getEnum(); } } return nullptr; } }; static kj::StringPtr getValueUnionFieldNameFor(schema::Type::Which type) { switch (type) { case schema::Type::VOID: return "void"; case schema::Type::BOOL: return "bool"; case schema::Type::INT8: return "int8"; case schema::Type::INT16: return "int16"; case schema::Type::INT32: return "int32"; case schema::Type::INT64: return "int64"; case schema::Type::UINT8: return "uint8"; case schema::Type::UINT16: return "uint16"; case schema::Type::UINT32: return "uint32"; case schema::Type::UINT64: return "uint64"; case schema::Type::FLOAT32: return "float32"; case schema::Type::FLOAT64: return "float64"; case schema::Type::TEXT: return "text"; case schema::Type::DATA: return "data"; case schema::Type::LIST: return "list"; case schema::Type::ENUM: return "enum"; case schema::Type::STRUCT: return "struct"; case schema::Type::INTERFACE: return "interface"; case schema::Type::OBJECT: return "object"; } KJ_FAIL_ASSERT("Unknown type."); } void NodeTranslator::compileBootstrapValue(ValueExpression::Reader source, schema::Type::Reader type, schema::Value::Builder target) { // Start by filling in a default default value so that if for whatever reason we don't end up // initializing the value, this won't cause schema validation to fail. compileDefaultDefaultValue(type, target); switch (type.which()) { case schema::Type::LIST: case schema::Type::STRUCT: case schema::Type::INTERFACE: case schema::Type::OBJECT: unfinishedValues.add(UnfinishedValue { source, type, target }); break; default: // Primitive value. compileValue(source, type, target, true); break; } } void NodeTranslator::compileValue(ValueExpression::Reader source, schema::Type::Reader type, schema::Value::Builder target, bool isBootstrap) { auto valueUnion = toDynamic(target); auto field = valueUnion.getSchema().getFieldByName( getValueUnionFieldNameFor(type.which())); switch (type.which()) { case schema::Type::LIST: KJ_IF_MAYBE(listSchema, makeListSchemaOf(type.getList())) { DynamicSlot slot(valueUnion, field, *listSchema); compileValue(source, slot, isBootstrap); } break; case schema::Type::STRUCT: KJ_IF_MAYBE(structSchema, resolver.resolveBootstrapSchema(type.getStruct())) { DynamicSlot slot(valueUnion, field, structSchema->asStruct()); compileValue(source, slot, isBootstrap); } break; case schema::Type::ENUM: KJ_IF_MAYBE(enumSchema, resolver.resolveBootstrapSchema(type.getEnum())) { DynamicSlot slot(valueUnion, field, enumSchema->asEnum()); compileValue(source, slot, isBootstrap); } break; default: DynamicSlot slot(valueUnion, field); compileValue(source, slot, isBootstrap); break; } } void NodeTranslator::compileValue(ValueExpression::Reader src, DynamicSlot& dst, bool isBootstrap) { // We rely on the dynamic API to detect type errors and throw exceptions. // // TODO(cleanup): We should perhaps ensure that all exceptions that this might throw are // recoverable, so that this doesn't crash if -fno-exceptions is enabled. Or create a better // way to test for type compatibility without throwing. KJ_IF_MAYBE(exception, kj::runCatchingExceptions( [&]() { compileValueInner(src, dst, isBootstrap); })) { errorReporter.addErrorOn(src, "Type mismatch."); } } void NodeTranslator::compileValueInner( ValueExpression::Reader src, DynamicSlot& dst, bool isBootstrap) { switch (src.which()) { case ValueExpression::NAME: { auto name = src.getName(); bool isBare = name.getBase().isRelativeName() && name.getMemberPath().size() == 0; bool wasSet = false; if (isBare) { // The name is just a bare identifier. It may be a literal value or an enumerant. kj::StringPtr id = name.getBase().getRelativeName().getValue(); KJ_IF_MAYBE(enumId, dst.getEnumType()) { KJ_IF_MAYBE(enumSchema, resolver.resolveBootstrapSchema(*enumId)) { KJ_IF_MAYBE(enumerant, enumSchema->asEnum().findEnumerantByName(id)) { dst.set(DynamicEnum(*enumerant)); wasSet = true; } } else { // Enum type is broken. We don't want to report a redundant error here, so just assume // we would have found a matching enumerant. dst.set(kj::implicitCast<uint16_t>(0)); wasSet = true; } } else { // Interpret known constant values. if (id == "void") { dst.set(VOID); wasSet = true; } else if (id == "true") { dst.set(true); wasSet = true; } else if (id == "false") { dst.set(false); wasSet = true; } else if (id == "nan") { dst.set(std::numeric_limits<double>::quiet_NaN()); wasSet = true; } else if (id == "inf") { dst.set(std::numeric_limits<double>::infinity()); wasSet = true; } } } if (!wasSet) { // Haven't resolved the name yet. Try looking up a constant. KJ_IF_MAYBE(constValue, readConstant(src.getName(), isBootstrap, src)) { dst.set(*constValue); } } break; } case ValueExpression::POSITIVE_INT: dst.set(src.getPositiveInt()); break; case ValueExpression::NEGATIVE_INT: { uint64_t nValue = src.getNegativeInt(); if (nValue > (std::numeric_limits<uint64_t>::max() >> 1) + 1) { errorReporter.addErrorOn(src, "Integer is too big to be negative."); } else { dst.set(kj::implicitCast<int64_t>(-nValue)); } break; } case ValueExpression::FLOAT: dst.set(src.getFloat()); break; case ValueExpression::STRING: dst.set(src.getString()); break; case ValueExpression::LIST: { auto srcList = src.getList(); auto dstList = dst.initList(srcList.size()); for (uint i = 0; i < srcList.size(); i++) { DynamicSlot slot(dstList, i); compileValue(srcList[i], slot, isBootstrap); } break; } case ValueExpression::STRUCT: { auto srcStruct = src.getStruct(); auto dstStruct = dst.initStruct(); auto dstSchema = dstStruct.getSchema(); for (auto assignment: srcStruct) { auto fieldName = assignment.getFieldName(); KJ_IF_MAYBE(field, dstSchema.findFieldByName(fieldName.getValue())) { DynamicSlot slot(dstStruct, *field); compileValue(assignment.getValue(), slot, isBootstrap); } else { errorReporter.addErrorOn(fieldName, kj::str( "Struct has no field named '", fieldName.getValue(), "'.")); } } break; } case ValueExpression::UNKNOWN: // Ignore earlier error. break; } } kj::Maybe<DynamicValue::Reader> NodeTranslator::readConstant( DeclName::Reader name, bool isBootstrap, ValueExpression::Reader errorLocation) { KJ_IF_MAYBE(resolved, resolver.resolve(name)) { if (resolved->kind != Declaration::CONST) { errorReporter.addErrorOn(errorLocation, kj::str("'", declNameString(name), "' does not refer to a constant.")); return nullptr; } // If we're bootstrapping, then we know we're expecting a primitive value, so if the // constant turns out to be non-primitive, we'll error out anyway. If we're not // bootstrapping, we may be compiling a non-primitive value and so we need the final // version of the constant to make sure its value is filled in. kj::Maybe<schema::Node::Reader> maybeConstSchema = isBootstrap ? resolver.resolveBootstrapSchema(resolved->id).map([](Schema s) { return s.getProto(); }) : resolver.resolveFinalSchema(resolved->id); KJ_IF_MAYBE(constSchema, maybeConstSchema) { auto constReader = constSchema->getConst(); auto dynamicConst = toDynamic(constReader.getValue()); auto constValue = dynamicConst.get(KJ_ASSERT_NONNULL(dynamicConst.which())); if (constValue.getType() == DynamicValue::OBJECT) { // We need to assign an appropriate schema to this object. DynamicObject objValue = constValue.as<DynamicObject>(); auto constType = constReader.getType(); switch (constType.which()) { case schema::Type::STRUCT: KJ_IF_MAYBE(structSchema, resolver.resolveBootstrapSchema(constType.getStruct())) { constValue = objValue.as(structSchema->asStruct()); } else { // The struct's schema is broken for reasons already reported. return nullptr; } break; case schema::Type::LIST: KJ_IF_MAYBE(listSchema, makeListSchemaOf(constType.getList())) { constValue = objValue.as(*listSchema); } else { // The list's schema is broken for reasons already reported. return nullptr; } break; case schema::Type::OBJECT: // Fine as-is. break; default: KJ_FAIL_ASSERT("Unrecognized Object-typed member of schema::Value."); break; } } if (name.getBase().isRelativeName() && name.getMemberPath().size() == 0) { // A fully unqualified identifier looks like it might refer to a constant visible in the // current scope, but if that's really what the user wanted, we want them to use a // qualified name to make it more obvious. Report an error. KJ_IF_MAYBE(scope, resolver.resolveBootstrapSchema(constSchema->getScopeId())) { auto scopeReader = scope->getProto(); kj::StringPtr parent; if (scopeReader.isFile()) { parent = ""; } else { parent = scopeReader.getDisplayName().slice(scopeReader.getDisplayNamePrefixLength()); } kj::StringPtr id = name.getBase().getRelativeName().getValue(); errorReporter.addErrorOn(errorLocation, kj::str( "Constant names must be qualified to avoid confusion. Please replace '", declNameString(name), "' with '", parent, ".", id, "', if that's what you intended.")); } } return constValue; } else { // The target is a constant, but the constant's schema is broken for reasons already reported. return nullptr; } } else { // Lookup will have reported an error. return nullptr; } } kj::Maybe<ListSchema> NodeTranslator::makeListSchemaOf(schema::Type::Reader elementType) { switch (elementType.which()) { case schema::Type::ENUM: KJ_IF_MAYBE(enumSchema, resolver.resolveBootstrapSchema(elementType.getEnum())) { return ListSchema::of(enumSchema->asEnum()); } else { return nullptr; } case schema::Type::STRUCT: KJ_IF_MAYBE(structSchema, resolver.resolveBootstrapSchema(elementType.getStruct())) { return ListSchema::of(structSchema->asStruct()); } else { return nullptr; } case schema::Type::INTERFACE: KJ_IF_MAYBE(interfaceSchema, resolver.resolveBootstrapSchema(elementType.getInterface())) { return ListSchema::of(interfaceSchema->asInterface()); } else { return nullptr; } case schema::Type::LIST: KJ_IF_MAYBE(listSchema, makeListSchemaOf(elementType.getList())) { return ListSchema::of(*listSchema); } else { return nullptr; } default: return ListSchema::of(elementType.which()); } } Orphan<List<schema::Annotation>> NodeTranslator::compileAnnotationApplications( List<Declaration::AnnotationApplication>::Reader annotations, kj::StringPtr targetsFlagName) { if (annotations.size() == 0 || !compileAnnotations) { // Return null. return Orphan<List<schema::Annotation>>(); } Orphanage orphanage = Orphanage::getForMessageContaining(wipNode.get()); auto result = orphanage.newOrphan<List<schema::Annotation>>(annotations.size()); auto builder = result.get(); for (uint i = 0; i < annotations.size(); i++) { Declaration::AnnotationApplication::Reader annotation = annotations[i]; schema::Annotation::Builder annotationBuilder = builder[i]; // Set the annotation's value to void in case we fail to produce something better below. annotationBuilder.initValue().setVoid(); auto name = annotation.getName(); KJ_IF_MAYBE(decl, resolver.resolve(name)) { if (decl->kind != Declaration::ANNOTATION) { errorReporter.addErrorOn(name, kj::str( "'", declNameString(name), "' is not an annotation.")); } else { annotationBuilder.setId(decl->id); KJ_IF_MAYBE(annotationSchema, resolver.resolveBootstrapSchema(decl->id)) { auto node = annotationSchema->getProto().getAnnotation(); if (!toDynamic(node).get(targetsFlagName).as<bool>()) { errorReporter.addErrorOn(name, kj::str( "'", declNameString(name), "' cannot be applied to this kind of declaration.")); } // Interpret the value. auto value = annotation.getValue(); switch (value.which()) { case Declaration::AnnotationApplication::Value::NONE: // No value, i.e. void. if (node.getType().isVoid()) { annotationBuilder.getValue().setVoid(); } else { errorReporter.addErrorOn(name, kj::str( "'", declNameString(name), "' requires a value.")); compileDefaultDefaultValue(node.getType(), annotationBuilder.getValue()); } break; case Declaration::AnnotationApplication::Value::EXPRESSION: compileBootstrapValue(value.getExpression(), node.getType(), annotationBuilder.getValue()); break; } } } } } return result; } } // namespace compiler } // namespace capnp