blob: 0d680a2df79a8514c26b0a4b94f13643d3ab3d7b [file] [log] [blame]
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_WASM_STREAMING_DECODER_H_
#define V8_WASM_STREAMING_DECODER_H_
#include <memory>
#include <vector>
#include "src/base/macros.h"
#include "src/utils/vector.h"
#include "src/wasm/compilation-environment.h"
#include "src/wasm/wasm-constants.h"
#include "src/wasm/wasm-result.h"
namespace v8 {
namespace internal {
namespace wasm {
class NativeModule;
// This class is an interface for the StreamingDecoder to start the processing
// of the incoming module bytes.
class V8_EXPORT_PRIVATE StreamingProcessor {
public:
virtual ~StreamingProcessor() = default;
// Process the first 8 bytes of a WebAssembly module. Returns true if the
// processing finished successfully and the decoding should continue.
virtual bool ProcessModuleHeader(Vector<const uint8_t> bytes,
uint32_t offset) = 0;
// Process all sections but the code section. Returns true if the processing
// finished successfully and the decoding should continue.
virtual bool ProcessSection(SectionCode section_code,
Vector<const uint8_t> bytes, uint32_t offset) = 0;
// Process the start of the code section. Returns true if the processing
// finished successfully and the decoding should continue.
virtual bool ProcessCodeSectionHeader(int num_functions, uint32_t offset,
std::shared_ptr<WireBytesStorage>) = 0;
// Process a function body. Returns true if the processing finished
// successfully and the decoding should continue.
virtual bool ProcessFunctionBody(Vector<const uint8_t> bytes,
uint32_t offset) = 0;
// Report the end of a chunk.
virtual void OnFinishedChunk() = 0;
// Report the end of the stream. If the stream was successful, all
// received bytes are passed by parameter. If there has been an error, an
// empty array is passed.
virtual void OnFinishedStream(OwnedVector<uint8_t> bytes) = 0;
// Report an error detected in the StreamingDecoder.
virtual void OnError(const WasmError&) = 0;
// Report the abortion of the stream.
virtual void OnAbort() = 0;
// Attempt to deserialize the module. Supports embedder caching.
virtual bool Deserialize(Vector<const uint8_t> module_bytes,
Vector<const uint8_t> wire_bytes) = 0;
};
// The StreamingDecoder takes a sequence of byte arrays, each received by a call
// of {OnBytesReceived}, and extracts the bytes which belong to section payloads
// and function bodies.
class V8_EXPORT_PRIVATE StreamingDecoder {
public:
explicit StreamingDecoder(std::unique_ptr<StreamingProcessor> processor);
// The buffer passed into OnBytesReceived is owned by the caller.
void OnBytesReceived(Vector<const uint8_t> bytes);
void Finish();
void Abort();
// Notify the StreamingDecoder that compilation ended and the
// StreamingProcessor should not be called anymore.
void NotifyCompilationEnded() { Fail(); }
// Caching support.
// Sets the callback that is called after the module is fully compiled.
using ModuleCompiledCallback =
std::function<void(const std::shared_ptr<NativeModule>&)>;
void SetModuleCompiledCallback(ModuleCompiledCallback callback);
// Passes previously compiled module bytes from the embedder's cache.
bool SetCompiledModuleBytes(Vector<const uint8_t> compiled_module_bytes);
void NotifyNativeModuleCreated(
const std::shared_ptr<NativeModule>& native_module);
private:
// TODO(ahaas): Put the whole private state of the StreamingDecoder into the
// cc file (PIMPL design pattern).
// The SectionBuffer is the data object for the content of a single section.
// It stores all bytes of the section (including section id and section
// length), and the offset where the actual payload starts.
class SectionBuffer : public WireBytesStorage {
public:
// id: The section id.
// payload_length: The length of the payload.
// length_bytes: The section length, as it is encoded in the module bytes.
SectionBuffer(uint32_t module_offset, uint8_t id, size_t payload_length,
Vector<const uint8_t> length_bytes)
: // ID + length + payload
module_offset_(module_offset),
bytes_(OwnedVector<uint8_t>::New(1 + length_bytes.length() +
payload_length)),
payload_offset_(1 + length_bytes.length()) {
bytes_.start()[0] = id;
memcpy(bytes_.start() + 1, &length_bytes.first(), length_bytes.length());
}
SectionCode section_code() const {
return static_cast<SectionCode>(bytes_.start()[0]);
}
Vector<const uint8_t> GetCode(WireBytesRef ref) const final {
DCHECK_LE(module_offset_, ref.offset());
uint32_t offset_in_code_buffer = ref.offset() - module_offset_;
return bytes().SubVector(offset_in_code_buffer,
offset_in_code_buffer + ref.length());
}
uint32_t module_offset() const { return module_offset_; }
Vector<uint8_t> bytes() const { return bytes_.as_vector(); }
Vector<uint8_t> payload() const { return bytes() + payload_offset_; }
size_t length() const { return bytes_.size(); }
size_t payload_offset() const { return payload_offset_; }
private:
const uint32_t module_offset_;
const OwnedVector<uint8_t> bytes_;
const size_t payload_offset_;
};
// The decoding of a stream of wasm module bytes is organized in states. Each
// state provides a buffer to store the bytes required for the current state,
// information on how many bytes have already been received, how many bytes
// are needed, and a {Next} function which starts the next state once all
// bytes of the current state were received.
//
// The states change according to the following state diagram:
//
// Start
// |
// |
// v
// DecodeModuleHeader
// | _________________________________________
// | | |
// v v |
// DecodeSectionID --> DecodeSectionLength --> DecodeSectionPayload
// A |
// | | (if the section id == code)
// | v
// | DecodeNumberOfFunctions -- > DecodeFunctionLength
// | A |
// | | |
// | (after all functions were read) | v
// ------------------------------------- DecodeFunctionBody
//
class DecodingState {
public:
virtual ~DecodingState() = default;
// Reads the bytes for the current state and returns the number of read
// bytes.
virtual size_t ReadBytes(StreamingDecoder* streaming,
Vector<const uint8_t> bytes);
// Returns the next state of the streaming decoding.
virtual std::unique_ptr<DecodingState> Next(
StreamingDecoder* streaming) = 0;
// The buffer to store the received bytes.
virtual Vector<uint8_t> buffer() = 0;
// The number of bytes which were already received.
size_t offset() const { return offset_; }
void set_offset(size_t value) { offset_ = value; }
// A flag to indicate if finishing the streaming decoder is allowed without
// error.
virtual bool is_finishing_allowed() const { return false; }
private:
size_t offset_ = 0;
};
// Forward declarations of the concrete states. This is needed so that they
// can access private members of the StreamingDecoder.
class DecodeVarInt32;
class DecodeModuleHeader;
class DecodeSectionID;
class DecodeSectionLength;
class DecodeSectionPayload;
class DecodeNumberOfFunctions;
class DecodeFunctionLength;
class DecodeFunctionBody;
// Creates a buffer for the next section of the module.
SectionBuffer* CreateNewBuffer(uint32_t module_offset, uint8_t section_id,
size_t length,
Vector<const uint8_t> length_bytes);
std::unique_ptr<DecodingState> Error(const WasmError& error) {
if (ok()) processor_->OnError(error);
Fail();
return std::unique_ptr<DecodingState>(nullptr);
}
std::unique_ptr<DecodingState> Error(std::string message) {
return Error(WasmError{module_offset_ - 1, std::move(message)});
}
void ProcessModuleHeader() {
if (!ok()) return;
if (!processor_->ProcessModuleHeader(state_->buffer(), 0)) Fail();
}
void ProcessSection(SectionBuffer* buffer) {
if (!ok()) return;
if (!processor_->ProcessSection(
buffer->section_code(), buffer->payload(),
buffer->module_offset() +
static_cast<uint32_t>(buffer->payload_offset()))) {
Fail();
}
}
void StartCodeSection(int num_functions,
std::shared_ptr<WireBytesStorage> wire_bytes_storage) {
if (!ok()) return;
// The offset passed to {ProcessCodeSectionHeader} is an error offset and
// not the start offset of a buffer. Therefore we need the -1 here.
if (!processor_->ProcessCodeSectionHeader(num_functions,
module_offset() - 1,
std::move(wire_bytes_storage))) {
Fail();
}
}
void ProcessFunctionBody(Vector<const uint8_t> bytes,
uint32_t module_offset) {
if (!ok()) return;
if (!processor_->ProcessFunctionBody(bytes, module_offset)) Fail();
}
void Fail() {
// We reset the {processor_} field to represent failure. This also ensures
// that we do not accidentally call further methods on the processor after
// failure.
processor_.reset();
}
bool ok() const { return processor_ != nullptr; }
uint32_t module_offset() const { return module_offset_; }
bool deserializing() const { return !compiled_module_bytes_.empty(); }
std::unique_ptr<StreamingProcessor> processor_;
std::unique_ptr<DecodingState> state_;
std::vector<std::shared_ptr<SectionBuffer>> section_buffers_;
bool code_section_processed_ = false;
uint32_t module_offset_ = 0;
size_t total_size_ = 0;
// Caching support.
ModuleCompiledCallback module_compiled_callback_ = nullptr;
// We need wire bytes in an array for deserializing cached modules.
std::vector<uint8_t> wire_bytes_for_deserializing_;
Vector<const uint8_t> compiled_module_bytes_;
DISALLOW_COPY_AND_ASSIGN(StreamingDecoder);
};
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_STREAMING_DECODER_H_