// slang-serialize-riff.cpp #include "slang-serialize-riff.h" namespace Slang { // // RIFFSerialWriter // RIFFSerialWriter::RIFFSerialWriter(RIFF::ChunkBuilder* chunk, FourCC type) : _cursor(chunk) { _initialize(type); } RIFFSerialWriter::RIFFSerialWriter(RIFF::Builder& riff, FourCC type) : _cursor(riff) { _initialize(type); } RIFFSerialWriter::~RIFFSerialWriter() { // We need to flush any pending operations to // write objects into the object definition list chunk. // _flush(); } SerializationMode RIFFSerialWriter::getMode() { return SerializationMode::Write; } void RIFFSerialWriter::handleBool(bool& value) { _cursor.addDataChunk(value ? RIFFSerial::kTrueFourCC : RIFFSerial::kFalseFourCC, nullptr, 0); } void RIFFSerialWriter::handleInt8(int8_t& value) { _writeInt(value); } void RIFFSerialWriter::handleInt16(int16_t& value) { _writeInt(value); } void RIFFSerialWriter::handleInt32(Int32& value) { _writeInt(value); } void RIFFSerialWriter::handleInt64(Int64& value) { _writeInt(value); } void RIFFSerialWriter::handleUInt8(uint8_t& value) { _writeUInt(value); } void RIFFSerialWriter::handleUInt16(uint16_t& value) { _writeUInt(value); } void RIFFSerialWriter::handleUInt32(UInt32& value) { _writeUInt(value); } void RIFFSerialWriter::handleUInt64(UInt64& value) { _writeUInt(value); } void RIFFSerialWriter::handleFloat32(float& value) { _writeFloat(value); } void RIFFSerialWriter::handleFloat64(double& value) { _writeFloat(value); } void RIFFSerialWriter::handleString(String& value) { _cursor.addDataChunk(RIFFSerial::kStringFourCC, value.getBuffer(), value.getLength()); } void RIFFSerialWriter::_writeInt(Int64 value) { // We pick a 32-bit representation if it can // faithfully represent the value, and a 64-bit // representation otherwise. // if (Int32(value) == value) { auto v = Int32(value); _cursor.addDataChunk(RIFFSerial::kInt32FourCC, &v, sizeof(v)); } else { _cursor.addDataChunk(RIFFSerial::kInt64FourCC, &value, sizeof(value)); } } void RIFFSerialWriter::_writeUInt(UInt64 value) { // We pick a 32-bit representation if it can // faithfully represent the value, and a 64-bit // representation otherwise. // if (UInt32(value) == value) { auto v = UInt32(value); _cursor.addDataChunk(RIFFSerial::kUInt32FourCC, &v, sizeof(v)); } else { _cursor.addDataChunk(RIFFSerial::kUInt64FourCC, &value, sizeof(value)); } } void RIFFSerialWriter::_writeFloat(double value) { // We pick a 32-bit representation if it can // faithfully represent the value, and a 64-bit // representation otherwise. // if (float(value) == value) { auto v = float(value); _cursor.addDataChunk(RIFFSerial::kFloat32FourCC, &v, sizeof(v)); } else { _cursor.addDataChunk(RIFFSerial::kFloat64FourCC, &value, sizeof(value)); } } void RIFFSerialWriter::beginArray(Scope&) { _cursor.beginListChunk(RIFFSerial::kArrayFourCC); } void RIFFSerialWriter::endArray(Scope&) { _cursor.endChunk(); } void RIFFSerialWriter::beginDictionary(Scope&) { _cursor.beginListChunk(RIFFSerial::kDictionaryFourCC); } void RIFFSerialWriter::endDictionary(Scope&) { _cursor.endChunk(); } bool RIFFSerialWriter::hasElements() { return false; } void RIFFSerialWriter::beginStruct(Scope&) { _cursor.beginListChunk(RIFFSerial::kStructFourCC); } void RIFFSerialWriter::endStruct(Scope&) { _cursor.endChunk(); } void RIFFSerialWriter::beginVariant(Scope& scope) { beginStruct(scope); } void RIFFSerialWriter::endVariant(Scope& scope) { endStruct(scope); } void RIFFSerialWriter::handleFieldKey(char const* name, Int index) { // For now we are ignoring field keys, and treating // structs as basically equivalent to tuples. SLANG_UNUSED(name); SLANG_UNUSED(index); } void RIFFSerialWriter::beginTuple(Scope&) { _cursor.beginListChunk(RIFFSerial::kTupleFourCC); } void RIFFSerialWriter::endTuple(Scope&) { _cursor.endChunk(); } void RIFFSerialWriter::beginOptional(Scope&) { _cursor.beginListChunk(RIFFSerial::kOptionalFourCC); } void RIFFSerialWriter::endOptional(Scope&) { _cursor.endChunk(); } void RIFFSerialWriter::handleSharedPtr(void*& value, SerializerCallback callback, void* context) { // Because we are writing, we only care about the // pointer that is already present in `value`. // void* ptr = value; // The first special case we check for is a null pointer, // which we can serialize as an inline value. // if (ptr == nullptr) { _cursor.addDataChunk(RIFFSerial::kNullFourCC, nullptr, 0); return; } // Next, we check to see if we have encountered this // pointer before, in which case we've already allocated // an index for it in the object definition list, and // we can simply write a reference to that index. // if (auto found = _mapPtrToObjectIndex.tryGetValue(ptr)) { auto objectIndex = *found; _writeObjectReference(objectIndex); return; } // If we have a non-null pointer that we haven't seen // before, then we will allocate a new entry in the // object definition list, and the pointer itself // will be written as a reference to that entry. // auto objectIndex = ObjectIndex(_objects.getCount()); _mapPtrToObjectIndex.add(ptr, objectIndex); _writeObjectReference(objectIndex); // At this point we've correctly written the *reference* // to the object (and will be able to write further // references later if we see an identical pointer), // but we also need to make sure that the *definition* // of the object gets written into the object definition // list chunk. // // The `callback` that was passed in can be used to // write out the members of the object, but if we // simply invoked it here and now we would be at risk // of introducing unbounded recursion in cases where // the object graph contains very long pointer chains. // // (Note that we are not at risk of *infinite* recursion, // because we have already cached the index for the // object into `_mapPtrToObjectIndex`) // // We will simply add an entry to our `_objects` array // to represent the to-be-written object, and store // the pointer and callback there so that we can write // everything out later, in `_flush()`. // ObjectInfo objectInfo; objectInfo.ptr = ptr; objectInfo.callback = callback; objectInfo.context = context; _objects.add(objectInfo); } void RIFFSerialWriter::handleUniquePtr(void*& value, SerializerCallback callback, void* context) { // We treat all pointers as shared pointers, because there isn't really // an optimized representation we would want to use for the unique case. // handleSharedPtr(value, callback, context); } void RIFFSerialWriter::handleDeferredObjectContents( void* valuePtr, SerializerCallback callback, void* context) { // Because we are already deferring writing of the *entirety* of // an object's members as part of how `handleSharedPtr()` works, // we don't need to implement deferral at this juncture. // // (In practice the `handleDeferredObjectContents()` operation is // more for the benefit of reading than writing). // callback(valuePtr, this, context); } void RIFFSerialWriter::_writeObjectReference(ObjectIndex index) { _cursor.addDataChunk(RIFFSerial::kObjectReferenceFourCC, &index, sizeof(index)); } void RIFFSerialWriter::_initialize(FourCC type) { // The entire content that we write will be nested // in a single list chunk, with the type that was // passed in. // _cursor.beginListChunk(type); // The first child chunk needs to be the object // definition list chunk, so we create it up front. // _objectDefinitionListChunk = _cursor.addListChunk(RIFFSerial::kObjectDefinitionListFourCC); } void RIFFSerialWriter::_flush() { // At this point we might have zero or more object // waiting to be written into the object definition list // chunk, and we need to make sure that they all // get a chance to write their content out. // _cursor.setCurrentChunk(_objectDefinitionListChunk); // Note that we do *not* compute `_objects.getCount()` outside // of the loop here, because writing out one object definition // could cause other objects to be referenced, which could // in turn add more entries to `_objects` that need to be // written out. // while (_writtenObjectDefinitionCount < _objects.getCount()) { auto objectIndex = _writtenObjectDefinitionCount++; auto objectInfo = _objects[objectIndex]; // We shouldn't ever be putting a null pointer into the // object definition list; there is logic in `handleSharedPtr()` // that explicitly checks for a null pointer and does an // early-exit in that case. // SLANG_ASSERT(objectInfo.ptr); // The callback that was passed into `handleSharedPtr()` should // be able to write out the value of the pointed-to object. // // Note that we are passing the *address* of `objectInfo.ptr` // and not just its *value*, because this callback is used // for both reading and writing, and in the reading case it // needs to be invoked on a pointer-pointer (e.g., a `T**` when // serializing an object pointer `T*`) so that the callee // can set the pointed-to pointer to whatever object it // allocates or finds. // objectInfo.callback(&objectInfo.ptr, this, objectInfo.context); // TODO(tfoley): There is an important invariant here that // the callback had better only write *one* value, but // that is not currently being enforced. } } // // RIFFSerialReader // RIFFSerialReader::RIFFSerialReader(RIFF::Chunk const* chunk, FourCC type) : _cursor(chunk) { _initialize(type); } RIFFSerialReader::~RIFFSerialReader() { _flush(); } SerializationMode RIFFSerialReader::getMode() { return SerializationMode::Read; } void RIFFSerialReader::handleBool(bool& value) { switch (_peekChunkType()) { case RIFFSerial::kTrueFourCC: _advanceCursor(); value = true; break; case RIFFSerial::kFalseFourCC: _advanceCursor(); value = false; break; default: SLANG_UNEXPECTED("invalid format in RIFF"); break; } } void RIFFSerialReader::handleInt8(int8_t& value) { value = int8_t(_readInt()); } void RIFFSerialReader::handleInt16(int16_t& value) { value = int16_t(_readInt()); } void RIFFSerialReader::handleInt32(Int32& value) { value = Int32(_readInt()); } void RIFFSerialReader::handleInt64(Int64& value) { value = Int64(_readInt()); } void RIFFSerialReader::handleUInt8(uint8_t& value) { value = uint8_t(_readUInt()); } void RIFFSerialReader::handleUInt16(uint16_t& value) { value = uint16_t(_readUInt()); } void RIFFSerialReader::handleUInt32(UInt32& value) { value = UInt32(_readUInt()); } void RIFFSerialReader::handleUInt64(UInt64& value) { value = UInt64(_readUInt()); } void RIFFSerialReader::handleFloat32(float& value) { value = float(_readFloat()); } void RIFFSerialReader::handleFloat64(double& value) { value = double(_readFloat()); } void RIFFSerialReader::handleString(String& value) { if (_peekChunkType() != RIFFSerial::kStringFourCC) { SLANG_UNEXPECTED("invalid format in RIFF"); return; } auto dataChunk = as(_cursor); if (!dataChunk) { SLANG_UNEXPECTED("invalid format in RIFF"); return; } auto size = dataChunk->getPayloadSize(); value = String(); value.appendRepeatedChar(' ', size); dataChunk->writePayloadInto((char*)value.getBuffer(), size); _advanceCursor(); } void RIFFSerialReader::beginArray(Scope&) { _beginListChunk(RIFFSerial::kArrayFourCC); } void RIFFSerialReader::endArray(Scope&) { _endListChunk(); } void RIFFSerialReader::beginDictionary(Scope&) { _beginListChunk(RIFFSerial::kDictionaryFourCC); } void RIFFSerialReader::endDictionary(Scope&) { _endListChunk(); } bool RIFFSerialReader::hasElements() { return _cursor.get() != nullptr; } void RIFFSerialReader::beginStruct(Scope&) { _beginListChunk(RIFFSerial::kStructFourCC); } void RIFFSerialReader::endStruct(Scope&) { _endListChunk(); } void RIFFSerialReader::beginVariant(Scope& scope) { beginStruct(scope); } void RIFFSerialReader::endVariant(Scope& scope) { endStruct(scope); } void RIFFSerialReader::handleFieldKey(char const* name, Int index) { // For now we are ignoring field keys, and treating // structs as basically equivalent to tuples. SLANG_UNUSED(name); SLANG_UNUSED(index); } void RIFFSerialReader::beginTuple(Scope&) { _beginListChunk(RIFFSerial::kTupleFourCC); } void RIFFSerialReader::endTuple(Scope&) { _endListChunk(); } void RIFFSerialReader::beginOptional(Scope&) { _beginListChunk(RIFFSerial::kOptionalFourCC); } void RIFFSerialReader::endOptional(Scope&) { _endListChunk(); } RIFFSerialReader::ObjectIndex RIFFSerialReader::_readObjectReference() { if (_peekChunkType() != RIFFSerial::kObjectReferenceFourCC) { SLANG_UNEXPECTED("invalid format in RIFF"); UNREACHABLE_RETURN(false); } auto objectIndex = _readDataChunk(); SLANG_ASSERT(objectIndex >= 0 && objectIndex < _objects.getCount()); return objectIndex; } void RIFFSerialReader::handleSharedPtr(void*& value, SerializerCallback callback, void* context) { // The logic here largely mirrors what appears in // `RIFFSerialWriter::handleSharedPtr`. // // We first check for an explicitly written null pointer. // If we find one our work is very easy. // if (_peekChunkType() == RIFFSerial::kNullFourCC) { _advanceCursor(); value = nullptr; return; } // Otherwise, we expect to find a reference to // an object index. // // Note that `_readObjectReference()` already asserts // that the index is in-bounds, so we don't repeat // that test here. // auto objectIndex = _readObjectReference(); // Now we need to check if we've previously read in // a reference to the same object. // auto& objectInfo = _objects[objectIndex]; if (objectInfo.state != ObjectState::Unread) { // We've seen this object before, although it // is still possible that we are in the middle // of reading it as part of an invocation // of `handleSharedPtr()` further up the call // stack. // // If a non-nullpointer value has already been // written into the `objectInfo`, then that means // the callback that was run for the prior (or // in-flight) read operation has already allocated // or found an object and written it out. // In that case we will trust the value. // if (objectInfo.ptr == nullptr) { // It is possible that the pointer is null because // the callback that was invoked explicitly *chose* // to yield a null pointer (perhaps the application // is choosing not to deserialize some optional // piece of state). // // However, if there is still a callback in-flight // to read this object, and the pointer is null, // then we have reached a circular reference, // and need to signal an error. // if (objectInfo.state == ObjectState::ReadingInProgress) { SLANG_UNEXPECTED("circularity detected in RIFF deserialization"); } } value = objectInfo.ptr; return; } // At this point we are reading a reference to an // object index that has not yet been read at all. // SLANG_ASSERT(objectInfo.state == ObjectState::Unread); // We cannot return from this function until we have // stored a pointer into `value`, to represent the // deserialized object. // // Thus we will set ourselves up to start reading // from the relevant object definition, and invoke // the callback that was passed in. // // Calling into user-defined serialization logic from // within this function creates the possibility of // unbounded/infinite recursion, so it is vital that // the user is properly using `deferSerializeObjectContents()` // to delay reading data that isn't immediately // necessary. // // We will still set the `objectInfo.state` to reflect // this in-flight operation so that we can detect // a cirularity if one occurs at runtime. // objectInfo.state = ObjectState::ReadingInProgress; // We save/restore the current cursor around // the callback, because we need to be able // to return to the current state to continue // reading whatever comes after the pointer // we were invoked to read. // _pushCursor(); _cursor = objectInfo.definitionChunk; // Note that we are passing the address of `objectInfo.ptr`, // and `objectInfo` is a reference to an element of the // `_objects` array. Thus whenever the `callback` stores // a pointer into that output parameter, the value it writes // will automatically be visible to any subsequent calls // to `handleSharedPtr()`, even if they occur before // `callback` returns. // // Thus a "true" circularity can only occur if the callback // recursively reads a reference to the same object again // *before* it allocates the in-memory representation of // that objects and stores a pointer to it into the output // parameter. // callback(&objectInfo.ptr, this, context); _popCursor(); objectInfo.state = ObjectState::ReadingComplete; value = objectInfo.ptr; } void RIFFSerialReader::handleUniquePtr(void*& value, SerializerCallback callback, void* userData) { // We treat all pointers as shared pointers, because there isn't really // an optimized representation we would want to use for the unique case. // handleSharedPtr(value, callback, userData); } void RIFFSerialReader::handleDeferredObjectContents( void* valuePtr, SerializerCallback callback, void* context) { // Unlike the case in `RIFFSerialWriter::handleDeferredObjectContents()`, // we very much *do* want to delay invoking the callback until later. // // There is a kind of symmetry going on, where the writer delays the // callback passed to `handleSharedPtr()`, but *not* the callback // passed to `handleDeferredObjectContents()`, while the reader // does the opposite: immediately calls the callback in `handleSharedPtr()` // but delays calling it here. // We make sure to save the current `_cursor` value along with // the arguments that will be passed into the callback, so that // we can restore the reader to this state before invoking // the callbak in `_flush()`. DeferredAction deferredAction; deferredAction.savedCursor = _cursor; deferredAction.valuePtr = valuePtr; deferredAction.callback = callback; deferredAction.context = context; _deferredActions.add(deferredAction); } void RIFFSerialReader::_initialize(FourCC type) { // All of the content will have been serialized as a single RIFF // list chunk (possibly a root chunk if this content comprises // an entire file), with the given `type`. // _beginListChunk(type); // The first child chunk should be the object definition list // chunk, and we will proactively read through all of the // entries in that chunk to build up the `_objects` array. // // This operation takes linear time in the number of serialized // objects, independent of their size, because the RIFF chunk // headers allow us to skip over the content of each of these // object-definition chunks. // _beginListChunk(RIFFSerial::kObjectDefinitionListFourCC); while (auto objectDefinitionChunk = _cursor.get()) { ObjectInfo objectInfo; objectInfo.definitionChunk = objectDefinitionChunk; _objects.add(objectInfo); _advanceCursor(); } _endListChunk(); } void RIFFSerialReader::_flush() { // We need to flush any actions that were deferred // and are still pending. // while (_deferredActions.getCount() != 0) { // TODO: For simplicity we are using the `_deferredActions` // array as a stack (LIFO), but it would be good to // check whether there is a menaingful difference in how // large the array would need to grow for a FIFO vs. LIFO, // and pick the better option. // auto deferredAction = _deferredActions.getLast(); _deferredActions.removeLast(); _cursor = deferredAction.savedCursor; deferredAction.callback(deferredAction.valuePtr, this, deferredAction.context); } } FourCC RIFFSerialReader::_peekChunkType() { auto chunk = _cursor.get(); if (!chunk) return 0; return chunk->getType(); } Int64 RIFFSerialReader::_readInt() { switch (_peekChunkType()) { case RIFFSerial::kInt64FourCC: return _readDataChunk(); case RIFFSerial::kInt32FourCC: return _readDataChunk(); case RIFFSerial::kUInt32FourCC: return _readDataChunk(); case RIFFSerial::kUInt64FourCC: { auto uintValue = _readDataChunk(); if (Int64(uintValue) < 0) { SLANG_UNEXPECTED("signed/unsigned mismatch in RIFF"); } return Int64(uintValue); } default: SLANG_UNEXPECTED("invalid format in RIFF"); UNREACHABLE_RETURN(0); } } UInt64 RIFFSerialReader::_readUInt() { switch (_peekChunkType()) { case RIFFSerial::kUInt64FourCC: return _readDataChunk(); case RIFFSerial::kUInt32FourCC: return _readDataChunk(); case RIFFSerial::kInt32FourCC: case RIFFSerial::kInt64FourCC: { auto intValue = _readInt(); if (intValue < 0) { SLANG_UNEXPECTED("signed/unsigned mismatch in RIFF"); } return UInt64(intValue); } default: SLANG_UNEXPECTED("invalid format in RIFF"); UNREACHABLE_RETURN(0); } } double RIFFSerialReader::_readFloat() { switch (_peekChunkType()) { case RIFFSerial::kFloat32FourCC: return _readDataChunk(); case RIFFSerial::kFloat64FourCC: return _readDataChunk(); default: SLANG_UNEXPECTED("invalid format in RIFF"); UNREACHABLE_RETURN(0); } } void RIFFSerialReader::_readDataChunk(void* outData, size_t dataSize) { auto dataChunk = as(_cursor); if (!dataChunk) { SLANG_UNEXPECTED("invalid format in RIFF"); return; } auto size = dataChunk->getPayloadSize(); if (size < dataSize) { SLANG_UNEXPECTED("invalid format in RIFF"); return; } dataChunk->writePayloadInto(outData, dataSize); _advanceCursor(); } void RIFFSerialReader::_beginListChunk(FourCC type) { auto listChunk = as(_cursor); if (!listChunk || listChunk->getType() != type) { SLANG_UNEXPECTED("invalid format in RIFF"); } _advanceCursor(); _pushCursor(); _cursor = listChunk->getFirstChild(); } void RIFFSerialReader::_endListChunk() { _popCursor(); } void RIFFSerialReader::_advanceCursor() { _cursor = _cursor.getNextSibling(); } void RIFFSerialReader::_pushCursor() { _stack.add(_cursor); } void RIFFSerialReader::_popCursor() { SLANG_ASSERT(_stack.getCount() != 0); _cursor = _stack.getLast(); _stack.removeLast(); } } // namespace Slang