diff options
author | John Hodge <tpg@ucc.asn.au> | 2018-06-03 14:57:05 +0800 |
---|---|---|
committer | John Hodge <tpg@ucc.asn.au> | 2018-06-03 14:57:05 +0800 |
commit | bf8f8b4b4a9fe273451be59f68acafbe61968b83 (patch) | |
tree | 82993550cb3c88de0edbd55d79e4ea8e8cefffac /tools/standalone_miri/value.hpp | |
parent | 39b3cf53798683e496804f8322da2254b10850f4 (diff) | |
parent | a7fb27789a2b34543851d207120e2c0001ee9c27 (diff) | |
download | mrust-bf8f8b4b4a9fe273451be59f68acafbe61968b83.tar.gz |
Merge branch 'master' of https://github.com/thepowersgang/mrustc
Diffstat (limited to 'tools/standalone_miri/value.hpp')
-rw-r--r-- | tools/standalone_miri/value.hpp | 379 |
1 files changed, 228 insertions, 151 deletions
diff --git a/tools/standalone_miri/value.hpp b/tools/standalone_miri/value.hpp index 8b103210..b057b3c4 100644 --- a/tools/standalone_miri/value.hpp +++ b/tools/standalone_miri/value.hpp @@ -1,11 +1,16 @@ -// -// -// +/* + * mrustc Standalone MIRI + * - by John Hodge (Mutabah) + * + * value.hpp + * - Runtime values + */ #pragma once #include <vector> #include <memory> #include <cstdint> +#include <cstring> // memcpy #include <cassert> namespace HIR { @@ -14,19 +19,55 @@ namespace HIR { } class Allocation; struct Value; +struct ValueRef; struct FFIPointer { const char* source_function; void* ptr_value; + size_t size; }; -class AllocationPtr +class AllocationHandle { friend class Allocation; - void* m_ptr; + friend class RelocationPtr; + Allocation* m_ptr; + +private: + AllocationHandle(Allocation* p): + m_ptr(p) + { + } public: + AllocationHandle(): m_ptr(nullptr) {} + AllocationHandle(AllocationHandle&& x): m_ptr(x.m_ptr) { + x.m_ptr = nullptr; + } + AllocationHandle(const AllocationHandle& x); + ~AllocationHandle(); + AllocationHandle& operator=(const AllocationHandle& x) = delete; + AllocationHandle& operator=(AllocationHandle&& x) { + this->~AllocationHandle(); + this->m_ptr = x.m_ptr; + x.m_ptr = nullptr; + return *this; + } + + operator bool() const { return m_ptr != 0; } + const Allocation& operator*() const { assert(m_ptr); return *m_ptr; } + Allocation& operator*() { assert(m_ptr); return *m_ptr; } + const Allocation* operator->() const { assert(m_ptr); return m_ptr; } + Allocation* operator->() { assert(m_ptr); return m_ptr; } +}; + +// TODO: Split into RelocationPtr and AllocationHandle +class RelocationPtr +{ + void* m_ptr; + +public: enum class Ty { Allocation, @@ -35,26 +76,20 @@ public: FfiPointer, }; -private: - AllocationPtr(Allocation* p): - m_ptr(p) - { - } -public: - AllocationPtr(): m_ptr(nullptr) {} - AllocationPtr(AllocationPtr&& x): m_ptr(x.m_ptr) { + RelocationPtr(): m_ptr(nullptr) {} + RelocationPtr(RelocationPtr&& x): m_ptr(x.m_ptr) { x.m_ptr = nullptr; } - AllocationPtr(const AllocationPtr& x); - ~AllocationPtr(); - static AllocationPtr new_fcn(::HIR::Path p); - //static AllocationPtr new_rawdata(const void* buf, size_t len); - static AllocationPtr new_string(const ::std::string* s); // NOTE: The string must have a stable pointer - static AllocationPtr new_ffi(FFIPointer info); - - AllocationPtr& operator=(const AllocationPtr& x) = delete; - AllocationPtr& operator=(AllocationPtr&& x) { - this->~AllocationPtr(); + RelocationPtr(const RelocationPtr& x); + ~RelocationPtr(); + static RelocationPtr new_alloc(AllocationHandle h); + static RelocationPtr new_fcn(::HIR::Path p); + static RelocationPtr new_string(const ::std::string* s); // NOTE: The string must have a stable pointer + static RelocationPtr new_ffi(FFIPointer info); + + RelocationPtr& operator=(const RelocationPtr& x) = delete; + RelocationPtr& operator=(RelocationPtr&& x) { + this->~RelocationPtr(); this->m_ptr = x.m_ptr; x.m_ptr = nullptr; return *this; @@ -96,7 +131,7 @@ public: return static_cast<Ty>( reinterpret_cast<uintptr_t>(m_ptr) & 3 ); } - friend ::std::ostream& operator<<(::std::ostream& os, const AllocationPtr& x); + friend ::std::ostream& operator<<(::std::ostream& os, const RelocationPtr& x); private: void* get_ptr() const { return reinterpret_cast<void*>( reinterpret_cast<uintptr_t>(m_ptr) & ~3 ); @@ -107,15 +142,77 @@ struct Relocation // Offset within parent allocation where this relocation is performed. // TODO: Size? size_t slot_ofs; - AllocationPtr backing_alloc; + RelocationPtr backing_alloc; +}; + +// TODO: Split write and read +struct ValueCommonRead +{ + virtual RelocationPtr get_relocation(size_t ofs) const = 0; + virtual void read_bytes(size_t ofs, void* dst, size_t count) const = 0; + + uint8_t read_u8(size_t ofs) const { uint8_t rv; read_bytes(ofs, &rv, 1); return rv; } + uint16_t read_u16(size_t ofs) const { uint16_t rv; read_bytes(ofs, &rv, 2); return rv; } + uint32_t read_u32(size_t ofs) const { uint32_t rv; read_bytes(ofs, &rv, 4); return rv; } + uint64_t read_u64(size_t ofs) const { uint64_t rv; read_bytes(ofs, &rv, 8); return rv; } + int8_t read_i8(size_t ofs) const { return static_cast<int8_t>(read_u8(ofs)); } + int16_t read_i16(size_t ofs) const { return static_cast<int16_t>(read_u16(ofs)); } + int32_t read_i32(size_t ofs) const { return static_cast<int32_t>(read_u32(ofs)); } + int64_t read_i64(size_t ofs) const { return static_cast<int64_t>(read_u64(ofs)); } + float read_f32(size_t ofs) const { float rv; read_bytes(ofs, &rv, 4); return rv; } + double read_f64(size_t ofs) const { double rv; read_bytes(ofs, &rv, 8); return rv; } + uint64_t read_usize(size_t ofs) const; + int64_t read_isize(size_t ofs) const { return static_cast<int64_t>(read_usize(ofs)); } + + /// Read a pointer from the value, requiring at least `req_valid` valid bytes, saves avaliable space in `size` + void* read_pointer_unsafe(size_t rd_ofs, size_t req_valid, size_t& size, bool& is_mut) const; + /// Read a pointer, requiring `req_len` valid bytes + const void* read_pointer_const(size_t rd_ofs, size_t req_len) const { + size_t tmp; + bool is_mut; + return read_pointer_unsafe(rd_ofs, req_len, tmp, is_mut); + } + /// Read a pointer, not requiring that the target be initialised + void* read_pointer_uninit(size_t rd_ofs, size_t& out_size) { + bool is_mut; + void* rv = read_pointer_unsafe(rd_ofs, 0, out_size, is_mut); + if(!is_mut) + throw ""; + //LOG_FATAL("Attempting to get an uninit pointer to immutable data"); + return rv; + } + /// Read a pointer and return a ValueRef to it (mutable data) + ValueRef read_pointer_valref_mut(size_t rd_ofs, size_t size); }; -class Allocation +struct ValueCommonWrite: + public ValueCommonRead { - friend class AllocationPtr; + virtual void write_bytes(size_t ofs, const void* src, size_t count) = 0; + + void write_u8 (size_t ofs, uint8_t v) { write_bytes(ofs, &v, 1); } + void write_u16(size_t ofs, uint16_t v) { write_bytes(ofs, &v, 2); } + void write_u32(size_t ofs, uint32_t v) { write_bytes(ofs, &v, 4); } + void write_u64(size_t ofs, uint64_t v) { write_bytes(ofs, &v, 8); } + void write_i8 (size_t ofs, int8_t v) { write_u8 (ofs, static_cast<uint8_t >(v)); } + void write_i16(size_t ofs, int16_t v) { write_u16(ofs, static_cast<uint16_t>(v)); } + void write_i32(size_t ofs, int32_t v) { write_u32(ofs, static_cast<uint32_t>(v)); } + void write_i64(size_t ofs, int64_t v) { write_u64(ofs, static_cast<uint64_t>(v)); } + void write_f32(size_t ofs, float v) { write_bytes(ofs, &v, 4); } + void write_f64(size_t ofs, double v) { write_bytes(ofs, &v, 8); } + void write_usize(size_t ofs, uint64_t v); + void write_isize(size_t ofs, int64_t v) { write_usize(ofs, static_cast<uint64_t>(v)); } + virtual void write_ptr(size_t ofs, size_t ptr_ofs, RelocationPtr reloc) = 0; +}; + +class Allocation: + public ValueCommonWrite +{ + friend class AllocationHandle; size_t refcount; // TODO: Read-only flag? + bool is_freed = false; public: - static AllocationPtr new_alloc(size_t size); + static AllocationHandle new_alloc(size_t size); const uint8_t* data_ptr() const { return reinterpret_cast<const uint8_t*>(this->data.data()); } uint8_t* data_ptr() { return reinterpret_cast< uint8_t*>(this->data.data()); } @@ -125,17 +222,19 @@ public: ::std::vector<uint8_t> mask; ::std::vector<Relocation> relocations; - AllocationPtr get_relocation(size_t ofs) const { + RelocationPtr get_relocation(size_t ofs) const override { for(const auto& r : relocations) { if(r.slot_ofs == ofs) return r.backing_alloc; } - return AllocationPtr(); + return RelocationPtr(); + } + void mark_as_freed() { + is_freed = true; + relocations.clear(); + for(auto& v : mask) + v = 0; } - //void mark_as_freed() { - // for(auto& v : mask) - // v = 0; - //} void resize(size_t new_size); @@ -143,127 +242,102 @@ public: void mark_bytes_valid(size_t ofs, size_t size); Value read_value(size_t ofs, size_t size) const; - void read_bytes(size_t ofs, void* dst, size_t count) const; + void read_bytes(size_t ofs, void* dst, size_t count) const override; void write_value(size_t ofs, Value v); - void write_bytes(size_t ofs, const void* src, size_t count); - - // TODO: Make this block common - void write_u8 (size_t ofs, uint8_t v) { write_bytes(ofs, &v, 1); } - void write_u16(size_t ofs, uint16_t v) { write_bytes(ofs, &v, 2); } - void write_u32(size_t ofs, uint32_t v) { write_bytes(ofs, &v, 4); } - void write_u64(size_t ofs, uint64_t v) { write_bytes(ofs, &v, 8); } - void write_i8 (size_t ofs, int8_t v) { write_u8 (ofs, static_cast<uint8_t >(v)); } - void write_i16(size_t ofs, int16_t v) { write_u16(ofs, static_cast<uint16_t>(v)); } - void write_i32(size_t ofs, int32_t v) { write_u32(ofs, static_cast<uint32_t>(v)); } - void write_i64(size_t ofs, int64_t v) { write_u64(ofs, static_cast<uint64_t>(v)); } - void write_f32(size_t ofs, float v) { write_bytes(ofs, &v, 4); } - void write_f64(size_t ofs, double v) { write_bytes(ofs, &v, 8); } - void write_usize(size_t ofs, uint64_t v); - void write_isize(size_t ofs, int64_t v) { write_usize(ofs, static_cast<uint64_t>(v)); } - - uint8_t read_u8(size_t ofs) const { uint8_t rv; read_bytes(ofs, &rv, 1); return rv; } - uint16_t read_u16(size_t ofs) const { uint16_t rv; read_bytes(ofs, &rv, 2); return rv; } - uint32_t read_u32(size_t ofs) const { uint32_t rv; read_bytes(ofs, &rv, 4); return rv; } - uint64_t read_u64(size_t ofs) const { uint64_t rv; read_bytes(ofs, &rv, 8); return rv; } - int8_t read_i8(size_t ofs) const { return static_cast<int8_t>(read_u8(ofs)); } - int16_t read_i16(size_t ofs) const { return static_cast<int16_t>(read_u16(ofs)); } - int32_t read_i32(size_t ofs) const { return static_cast<int32_t>(read_u32(ofs)); } - int64_t read_i64(size_t ofs) const { return static_cast<int64_t>(read_u64(ofs)); } - float read_f32(size_t ofs) const { float rv; read_bytes(ofs, &rv, 4); return rv; } - double read_f64(size_t ofs) const { double rv; read_bytes(ofs, &rv, 8); return rv; } - uint64_t read_usize(size_t ofs) const; - int64_t read_isize(size_t ofs) const { return static_cast<int64_t>(read_usize(ofs)); } + void write_bytes(size_t ofs, const void* src, size_t count) override; + void write_ptr(size_t ofs, size_t ptr_ofs, RelocationPtr reloc) override; }; extern ::std::ostream& operator<<(::std::ostream& os, const Allocation& x); -struct Value +struct Value: + public ValueCommonWrite { // If NULL, data is direct - AllocationPtr allocation; + AllocationHandle allocation; struct { - uint8_t data[2*sizeof(size_t)-3]; // 16-3 = 13, fits in 16 bits of mask + // NOTE: Can't pack the mask+size tighter, need 4 bits of size (8-15) leaving 12 bits of mask + uint8_t data[2*8-3]; // 13 data bytes, plus 16bit mask, plus size = 16 bytes uint8_t mask[2]; uint8_t size; } direct_data; Value(); Value(::HIR::TypeRef ty); + static Value with_size(size_t size, bool have_allocation); static Value new_fnptr(const ::HIR::Path& fn_path); static Value new_ffiptr(FFIPointer ffi); + static Value new_pointer(::HIR::TypeRef ty, uint64_t v, RelocationPtr r); + static Value new_usize(uint64_t v); + static Value new_isize(int64_t v); + static Value new_u32(uint32_t v); + static Value new_i32(int32_t v); void create_allocation(); - size_t size() const { return allocation ? allocation.alloc().size() : direct_data.size; } + size_t size() const { return allocation ? allocation->size() : direct_data.size; } + const uint8_t* data_ptr() const { return allocation ? allocation->data_ptr() : direct_data.data; } + uint8_t* data_ptr() { return allocation ? allocation->data_ptr() : direct_data.data; } + + RelocationPtr get_relocation(size_t ofs) const override { + if( this->allocation && this->allocation ) + return this->allocation->get_relocation(ofs); + else + return RelocationPtr(); + } void check_bytes_valid(size_t ofs, size_t size) const; void mark_bytes_valid(size_t ofs, size_t size); Value read_value(size_t ofs, size_t size) const; - void read_bytes(size_t ofs, void* dst, size_t count) const; + void read_bytes(size_t ofs, void* dst, size_t count) const override; void write_value(size_t ofs, Value v); - void write_bytes(size_t ofs, const void* src, size_t count); + void write_bytes(size_t ofs, const void* src, size_t count) override; - // TODO: Make this block common - void write_u8 (size_t ofs, uint8_t v) { write_bytes(ofs, &v, 1); } - void write_u16(size_t ofs, uint16_t v) { write_bytes(ofs, &v, 2); } - void write_u32(size_t ofs, uint32_t v) { write_bytes(ofs, &v, 4); } - void write_u64(size_t ofs, uint64_t v) { write_bytes(ofs, &v, 8); } - void write_i8 (size_t ofs, int8_t v) { write_u8 (ofs, static_cast<uint8_t >(v)); } - void write_i16(size_t ofs, int16_t v) { write_u16(ofs, static_cast<uint16_t>(v)); } - void write_i32(size_t ofs, int32_t v) { write_u32(ofs, static_cast<uint32_t>(v)); } - void write_i64(size_t ofs, int64_t v) { write_u64(ofs, static_cast<uint64_t>(v)); } - void write_f32(size_t ofs, float v) { write_bytes(ofs, &v, 4); } - void write_f64(size_t ofs, double v) { write_bytes(ofs, &v, 8); } - void write_usize(size_t ofs, uint64_t v); - void write_isize(size_t ofs, int64_t v) { write_usize(ofs, static_cast<uint64_t>(v)); } - - uint8_t read_u8(size_t ofs) const { uint8_t rv; read_bytes(ofs, &rv, 1); return rv; } - uint16_t read_u16(size_t ofs) const { uint16_t rv; read_bytes(ofs, &rv, 2); return rv; } - uint32_t read_u32(size_t ofs) const { uint32_t rv; read_bytes(ofs, &rv, 4); return rv; } - uint64_t read_u64(size_t ofs) const { uint64_t rv; read_bytes(ofs, &rv, 8); return rv; } - int8_t read_i8(size_t ofs) const { return static_cast<int8_t>(read_u8(ofs)); } - int16_t read_i16(size_t ofs) const { return static_cast<int16_t>(read_u16(ofs)); } - int32_t read_i32(size_t ofs) const { return static_cast<int32_t>(read_u32(ofs)); } - int64_t read_i64(size_t ofs) const { return static_cast<int64_t>(read_u64(ofs)); } - float read_f32(size_t ofs) const { float rv; read_bytes(ofs, &rv, 4); return rv; } - double read_f64(size_t ofs) const { double rv; read_bytes(ofs, &rv, 8); return rv; } - uint64_t read_usize(size_t ofs) const; - int64_t read_isize(size_t ofs) const { return static_cast<int64_t>(read_usize(ofs)); } + void write_ptr(size_t ofs, size_t ptr_ofs, RelocationPtr reloc) override; }; extern ::std::ostream& operator<<(::std::ostream& os, const Value& v); // A read-only reference to a value (to write, you have to go through it) -struct ValueRef +struct ValueRef: + public ValueCommonRead { - // Either an AllocationPtr, or a Value pointer - AllocationPtr m_alloc; + // Either an AllocationHandle, or a Value pointer + RelocationPtr m_alloc; Value* m_value; size_t m_offset; // Offset within the value size_t m_size; // Size in bytes of the referenced value ::std::shared_ptr<Value> m_metadata; - ValueRef(AllocationPtr ptr, size_t ofs, size_t size): + ValueRef(RelocationPtr ptr, size_t ofs, size_t size): m_alloc(ptr), m_value(nullptr), m_offset(ofs), m_size(size) { - switch(m_alloc.get_ty()) + if( m_alloc ) { - case AllocationPtr::Ty::Allocation: - assert(ofs < m_alloc.alloc().size()); - assert(size <= m_alloc.alloc().size()); - assert(ofs+size <= m_alloc.alloc().size()); - break; - case AllocationPtr::Ty::StdString: - assert(ofs < m_alloc.str().size()); - assert(size <= m_alloc.str().size()); - assert(ofs+size <= m_alloc.str().size()); - break; - default: - throw "TODO"; + switch(m_alloc.get_ty()) + { + case RelocationPtr::Ty::Allocation: + assert(ofs < m_alloc.alloc().size()); + assert(size <= m_alloc.alloc().size()); + assert(ofs+size <= m_alloc.alloc().size()); + break; + case RelocationPtr::Ty::StdString: + assert(ofs < m_alloc.str().size()); + assert(size <= m_alloc.str().size()); + assert(ofs+size <= m_alloc.str().size()); + break; + case RelocationPtr::Ty::FfiPointer: + assert(ofs < m_alloc.ffi().size); + assert(size <= m_alloc.ffi().size); + assert(ofs+size <= m_alloc.ffi().size); + break; + default: + throw "TODO"; + } } } ValueRef(Value& val): @@ -277,56 +351,70 @@ struct ValueRef { } - AllocationPtr get_relocation(size_t ofs) const { + RelocationPtr get_relocation(size_t ofs) const override { if(m_alloc) { if( m_alloc.is_alloc() ) return m_alloc.alloc().get_relocation(ofs); else - return AllocationPtr(); + return RelocationPtr(); } - else if( m_value->allocation ) + else if( m_value ) { - if( m_value->allocation.is_alloc() ) - return m_value->allocation.alloc().get_relocation(ofs); - else - return AllocationPtr(); + return m_value->get_relocation(ofs); } else { - return AllocationPtr(); + return RelocationPtr(); + } + } + Value read_value(size_t ofs, size_t size) const; + const uint8_t* data_ptr() const { + if( m_alloc ) { + switch(m_alloc.get_ty()) + { + case RelocationPtr::Ty::Allocation: + return m_alloc.alloc().data_ptr() + m_offset; + break; + case RelocationPtr::Ty::StdString: + return reinterpret_cast<const uint8_t*>(m_alloc.str().data() + m_offset); + default: + throw "TODO"; + } + } + else if( m_value ) { + return m_value->data_ptr() + m_offset; + } + else { + return nullptr; } } - Value read_value(size_t ofs, size_t size) const { + void read_bytes(size_t ofs, void* dst, size_t size) const { if( size == 0 ) - return Value(); + return ; assert(ofs < m_size); assert(size <= m_size); assert(ofs+size <= m_size); if( m_alloc ) { switch(m_alloc.get_ty()) { - case AllocationPtr::Ty::Allocation: - return m_alloc.alloc().read_value(m_offset + ofs, size); - case AllocationPtr::Ty::StdString: { - auto rv = Value::with_size(size, false); - //ASSERT_BUG(ofs <= m_alloc.str().size(), ""); - //ASSERT_BUG(size <= m_alloc.str().size(), ""); - //ASSERT_BUG(ofs+size <= m_alloc.str().size(), ""); + case RelocationPtr::Ty::Allocation: + m_alloc.alloc().read_bytes(m_offset + ofs, dst, size); + break; + case RelocationPtr::Ty::StdString: assert(m_offset+ofs <= m_alloc.str().size() && size <= m_alloc.str().size() && m_offset+ofs+size <= m_alloc.str().size()); - rv.write_bytes(0, m_alloc.str().data() + m_offset + ofs, size); - return rv; - } + ::std::memcpy(dst, m_alloc.str().data() + m_offset + ofs, size); + break; default: //ASSERT_BUG(m_alloc.is_alloc(), "read_value on non-data backed Value - " << ); throw "TODO"; } } else { - return m_value->read_value(m_offset + ofs, size); + m_value->read_bytes(m_offset + ofs, dst, size); } } - void read_bytes(size_t ofs, void* dst, size_t size) const { + void check_bytes_valid(size_t ofs, size_t size) const { if( size == 0 ) return ; assert(ofs < m_size); @@ -335,12 +423,11 @@ struct ValueRef if( m_alloc ) { switch(m_alloc.get_ty()) { - case AllocationPtr::Ty::Allocation: - m_alloc.alloc().read_bytes(m_offset + ofs, dst, size); + case RelocationPtr::Ty::Allocation: + m_alloc.alloc().check_bytes_valid(m_offset + ofs, size); break; - case AllocationPtr::Ty::StdString: + case RelocationPtr::Ty::StdString: assert(m_offset+ofs <= m_alloc.str().size() && size <= m_alloc.str().size() && m_offset+ofs+size <= m_alloc.str().size()); - ::std::memcpy(dst, m_alloc.str().data() + m_offset + ofs, size); break; default: //ASSERT_BUG(m_alloc.is_alloc(), "read_value on non-data backed Value - " << ); @@ -348,20 +435,10 @@ struct ValueRef } } else { - m_value->read_bytes(m_offset + ofs, dst, size); + m_value->check_bytes_valid(m_offset + ofs, size); } } - uint8_t read_u8(size_t ofs) const { uint8_t rv; read_bytes(ofs, &rv, 1); return rv; } - uint16_t read_u16(size_t ofs) const { uint16_t rv; read_bytes(ofs, &rv, 2); return rv; } - uint32_t read_u32(size_t ofs) const { uint32_t rv; read_bytes(ofs, &rv, 4); return rv; } - uint64_t read_u64(size_t ofs) const { uint64_t rv; read_bytes(ofs, &rv, 8); return rv; } - int8_t read_i8(size_t ofs) const { return static_cast<int8_t>(read_u8(ofs)); } - int16_t read_i16(size_t ofs) const { return static_cast<int16_t>(read_u16(ofs)); } - int32_t read_i32(size_t ofs) const { return static_cast<int32_t>(read_u32(ofs)); } - int64_t read_i64(size_t ofs) const { return static_cast<int64_t>(read_u64(ofs)); } - float read_f32(size_t ofs) const { float rv; read_bytes(ofs, &rv, 4); return rv; } - double read_f64(size_t ofs) const { double rv; read_bytes(ofs, &rv, 8); return rv; } - uint64_t read_usize(size_t ofs) const; - int64_t read_isize(size_t ofs) const { return static_cast<int64_t>(read_usize(ofs)); } + + bool compare(const void* other, size_t other_len) const; }; extern ::std::ostream& operator<<(::std::ostream& os, const ValueRef& v); |