diff options
author | John Hodge <tpg@mutabah.net> | 2018-05-19 12:29:44 +0800 |
---|---|---|
committer | John Hodge <tpg@mutabah.net> | 2018-05-19 12:29:44 +0800 |
commit | a96b446e80f109138e2a639ec94222017af0b9b1 (patch) | |
tree | 36325cf64577c9de69fad493031460ecaabb1c6d | |
parent | ae177706bf0b4b2ff05e9102d1403c73799756b0 (diff) | |
download | mrust-a96b446e80f109138e2a639ec94222017af0b9b1.tar.gz |
Standalone MIRI - Use some more helpers
-rwxr-xr-x | test_smiri.sh | 4 | ||||
-rw-r--r-- | tools/standalone_miri/hir_sim.cpp | 14 | ||||
-rw-r--r-- | tools/standalone_miri/hir_sim.hpp | 7 | ||||
-rw-r--r-- | tools/standalone_miri/main.cpp | 18 | ||||
-rw-r--r-- | tools/standalone_miri/miri.cpp | 104 | ||||
-rw-r--r-- | tools/standalone_miri/value.cpp | 41 | ||||
-rw-r--r-- | tools/standalone_miri/value.hpp | 14 |
7 files changed, 117 insertions, 85 deletions
diff --git a/test_smiri.sh b/test_smiri.sh index 5a7de4e4..0dbad3fa 100755 --- a/test_smiri.sh +++ b/test_smiri.sh @@ -2,5 +2,7 @@ set -e cd $(dirname $0) make -f minicargo.mk MMIR=1 LIBS +echo "--- mrustc -o output-mmir/hello" ./bin/mrustc rustc-1.19.0-src/src/test/run-pass/hello.rs -O -C codegen-type=monomir -o output-mmir/hello -L output-mmir/ > output-mmir/hello_dbg.txt -./tools/bin/standalone_miri output-mmir/hello.mir --logfile smiri_hello.log +echo "--- standalone_miri output-mmir/hello.mir" +time ./tools/bin/standalone_miri output-mmir/hello.mir --logfile smiri_hello.log diff --git a/tools/standalone_miri/hir_sim.cpp b/tools/standalone_miri/hir_sim.cpp index f3b4d400..94f0d0e1 100644 --- a/tools/standalone_miri/hir_sim.cpp +++ b/tools/standalone_miri/hir_sim.cpp @@ -20,6 +20,7 @@ size_t HIR::TypeRef::get_size(size_t ofs) const case RawType::Unit: return 0; case RawType::Composite: + // NOTE: Don't care if the type has metadata return this->composite_type->size; case RawType::Unreachable: LOG_BUG("Attempting to get size of an unreachable type, " << *this); @@ -53,7 +54,7 @@ size_t HIR::TypeRef::get_size(size_t ofs) const } throw ""; } - + switch(this->wrappers[ofs].type) { case TypeWrapper::Ty::Array: @@ -100,22 +101,25 @@ size_t HIR::TypeRef::get_size(size_t ofs) const } throw ""; } -bool HIR::TypeRef::has_slice_meta() const +bool HIR::TypeRef::has_slice_meta(size_t& out_inner_size) const { if( this->wrappers.size() == 0 ) { if(this->inner_type == RawType::Composite) { - // TODO: Handle metadata better + // TODO: This type could be wrapping a slice, needs to return the inner type size. + // - Also need to know which field is the unsized one return false; } else { + out_inner_size = 1; return (this->inner_type == RawType::Str); } } else { + out_inner_size = this->get_size(1); return (this->wrappers[0].type == TypeWrapper::Ty::Slice); } } @@ -130,9 +134,9 @@ HIR::TypeRef HIR::TypeRef::get_inner() const ity.wrappers.erase(ity.wrappers.begin()); return ity; } -HIR::TypeRef HIR::TypeRef::wrap(TypeWrapper::Ty ty, size_t size) const +HIR::TypeRef HIR::TypeRef::wrap(TypeWrapper::Ty ty, size_t size)&& { - auto rv = *this; + auto rv = ::std::move(*this); rv.wrappers.insert(rv.wrappers.begin(), { ty, size }); return rv; } diff --git a/tools/standalone_miri/hir_sim.hpp b/tools/standalone_miri/hir_sim.hpp index 1dc9bcc4..9f5ba59c 100644 --- a/tools/standalone_miri/hir_sim.hpp +++ b/tools/standalone_miri/hir_sim.hpp @@ -118,11 +118,14 @@ namespace HIR { } size_t get_size(size_t ofs=0) const; - bool has_slice_meta() const; // The attached metadata is a count + bool has_slice_meta(size_t& out_inner_size) const; // The attached metadata is a count of elements const TypeRef* get_usized_type(size_t& running_inner_size) const; TypeRef get_meta_type() const; TypeRef get_inner() const; - TypeRef wrap(TypeWrapper::Ty ty, size_t size) const; + TypeRef wrap(TypeWrapper::Ty ty, size_t size)&&; + TypeRef wrapped(TypeWrapper::Ty ty, size_t size) const { + return TypeRef(*this).wrap(ty, size); + } TypeRef get_field(size_t idx, size_t& ofs) const; bool operator==(const RawType& x) const { diff --git a/tools/standalone_miri/main.cpp b/tools/standalone_miri/main.cpp index c214676a..e3a8d22e 100644 --- a/tools/standalone_miri/main.cpp +++ b/tools/standalone_miri/main.cpp @@ -50,15 +50,7 @@ int main(int argc, const char* argv[]) auto tree = ModuleTree {}; tree.load_file(opts.infile); - // Construct argc/argv values - auto val_argc = Value( ::HIR::TypeRef{RawType::ISize} ); - ::HIR::TypeRef argv_ty { RawType::I8 }; - argv_ty.wrappers.push_back(TypeWrapper { TypeWrapper::Ty::Pointer, 0 }); - argv_ty.wrappers.push_back(TypeWrapper { TypeWrapper::Ty::Pointer, 0 }); - auto val_argv = Value(argv_ty); - // Create argc/argv based on input arguments - val_argc.write_isize(0, 1 + opts.args.size()); auto argv_alloc = Allocation::new_alloc((1 + opts.args.size()) * POINTER_SIZE); argv_alloc->write_usize(0 * POINTER_SIZE, 0); argv_alloc->relocations.push_back({ 0 * POINTER_SIZE, RelocationPtr::new_ffi(FFIPointer { "", (void*)(opts.infile.c_str()), opts.infile.size() + 1 }) }); @@ -67,9 +59,13 @@ int main(int argc, const char* argv[]) argv_alloc->write_usize((1 + i) * POINTER_SIZE, 0); argv_alloc->relocations.push_back({ (1 + i) * POINTER_SIZE, RelocationPtr::new_ffi({ "", (void*)(opts.args[0]), ::std::strlen(opts.args[0]) + 1 }) }); } - //val_argv.write_ptr(0, 0, RelocationPtr::new_alloc(argv_alloc)); - val_argv.write_usize(0, 0); - val_argv.allocation->relocations.push_back({ 0 * POINTER_SIZE, RelocationPtr::new_alloc(argv_alloc) }); + + // Construct argc/argv values + auto val_argc = Value::new_isize(1 + opts.args.size()); + ::HIR::TypeRef argv_ty { RawType::I8 }; + argv_ty.wrappers.push_back(TypeWrapper { TypeWrapper::Ty::Pointer, 0 }); + argv_ty.wrappers.push_back(TypeWrapper { TypeWrapper::Ty::Pointer, 0 }); + auto val_argv = Value::new_pointer(argv_ty, 0, RelocationPtr::new_alloc(argv_alloc)); // Catch various exceptions from the interpreter try diff --git a/tools/standalone_miri/miri.cpp b/tools/standalone_miri/miri.cpp index 733aa8a3..c0e7d15c 100644 --- a/tools/standalone_miri/miri.cpp +++ b/tools/standalone_miri/miri.cpp @@ -352,16 +352,18 @@ struct MirHelpers LOG_ASSERT(val.m_size == POINTER_SIZE + meta_size, "Deref of " << ty << ", but pointer isn't correct size"); meta_val = ::std::make_shared<Value>( val.read_value(POINTER_SIZE, meta_size) ); - // TODO: Get a more sane size from the metadata - if( alloc ) - { + size_t slice_inner_size; + if( ty.has_slice_meta(slice_inner_size) ) { + size = (ty.wrappers.empty() ? ty.get_size() : 0) + meta_val->read_usize(0) * slice_inner_size; + } + //else if( ty == RawType::TraitObject) { + // // NOTE: Getting the size from the allocation is semi-valid, as you can't sub-slice trait objects + // size = alloc.get_size() - ofs; + //} + else { LOG_DEBUG("> Meta " << *meta_val << ", size = " << alloc.get_size() << " - " << ofs); size = alloc.get_size() - ofs; } - else - { - size = 0; - } } else { @@ -406,6 +408,7 @@ struct MirHelpers } void write_lvalue(const ::MIR::LValue& lv, Value val) { + // TODO: Ensure that target is writable? Or should write_value do that? //LOG_DEBUG(lv << " = " << val); ::HIR::TypeRef ty; auto base_value = get_value_and_type(lv, ty); @@ -428,12 +431,14 @@ struct MirHelpers Value val = Value(ty); val.write_bytes(0, &ce.v, ::std::min(ty.get_size(), sizeof(ce.v))); // TODO: Endian // TODO: If the write was clipped, sign-extend + // TODO: i128/u128 need the upper bytes cleared+valid return val; } break; TU_ARM(c, Uint, ce) { ty = ::HIR::TypeRef(ce.t); Value val = Value(ty); val.write_bytes(0, &ce.v, ::std::min(ty.get_size(), sizeof(ce.v))); // TODO: Endian + // TODO: i128/u128 need the upper bytes cleared+valid return val; } break; TU_ARM(c, Bool, ce) { @@ -466,11 +471,9 @@ struct MirHelpers ty = ::HIR::TypeRef(RawType::Str); ty.wrappers.push_back(TypeWrapper { TypeWrapper::Ty::Borrow, 0 }); Value val = Value(ty); - val.write_usize(0, 0); + val.write_ptr(0, 0, RelocationPtr::new_string(&ce)); val.write_usize(POINTER_SIZE, ce.size()); - val.allocation->relocations.push_back(Relocation { 0, RelocationPtr::new_string(&ce) }); LOG_DEBUG(c << " = " << val); - //return Value::new_dataptr(ce.data()); return val; } break; // --> Accessor @@ -482,11 +485,8 @@ struct MirHelpers } if( const auto* s = this->thread.m_modtree.get_static_opt(ce) ) { ty = s->ty; - ty.wrappers.push_back(TypeWrapper { TypeWrapper::Ty::Borrow, 0 }); - Value val = Value(ty); - val.write_usize(0, 0); - val.allocation->relocations.push_back(Relocation { 0, RelocationPtr::new_alloc(s->val.allocation) }); - return val; + ty.wrappers.insert(ty.wrappers.begin(), TypeWrapper { TypeWrapper::Ty::Borrow, 0 }); + return Value::new_pointer(ty, 0, RelocationPtr::new_alloc(s->val.allocation)); } LOG_ERROR("Constant::ItemAddr - " << ce << " - not found"); } break; @@ -602,19 +602,17 @@ bool InterpreterThread::step_one(Value& out_thread_result) LOG_DEBUG("- alloc=" << alloc); size_t ofs = src_base_value.m_offset; const auto meta = src_ty.get_meta_type(); - //bool is_slice_like = src_ty.has_slice_meta(); src_ty.wrappers.insert(src_ty.wrappers.begin(), TypeWrapper { TypeWrapper::Ty::Borrow, static_cast<size_t>(re.type) }); + // Create the pointer new_val = Value(src_ty); - // ^ Pointer value - new_val.write_usize(0, ofs); + new_val.write_ptr(0, ofs, ::std::move(alloc)); + // - Add metadata if required if( meta != RawType::Unreachable ) { LOG_ASSERT(src_base_value.m_metadata, "Borrow of an unsized value, but no metadata avaliable"); new_val.write_value(POINTER_SIZE, *src_base_value.m_metadata); } - // - Add the relocation after writing the value (writing clears the relocations) - new_val.allocation->relocations.push_back(Relocation { 0, ::std::move(alloc) }); } break; TU_ARM(se.src, Cast, re) { // Determine the type of cast, is it a reinterpret or is it a value transform? @@ -1306,11 +1304,9 @@ bool InterpreterThread::step_one(Value& out_thread_result) size_t ofs = v.m_offset; assert(ty.get_meta_type() == RawType::Unreachable); - auto ptr_ty = ty.wrap(TypeWrapper::Ty::Borrow, 2); + auto ptr_ty = ty.wrapped(TypeWrapper::Ty::Borrow, 2); - auto ptr_val = Value(ptr_ty); - ptr_val.write_usize(0, ofs); - ptr_val.allocation->relocations.push_back(Relocation { 0, ::std::move(alloc) }); + auto ptr_val = Value::new_pointer(ptr_ty, ofs, ::std::move(alloc)); if( !drop_value(ptr_val, ty, /*shallow=*/se.kind == ::MIR::eDropKind::SHALLOW) ) { @@ -1327,7 +1323,7 @@ bool InterpreterThread::step_one(Value& out_thread_result) LOG_TODO(stmt); break; } - + cur_frame.stmt_idx += 1; } else @@ -1456,7 +1452,7 @@ bool InterpreterThread::step_one(Value& out_thread_result) } cur_frame.stmt_idx = 0; } - + return false; } bool InterpreterThread::pop_stack(Value& out_thread_result) @@ -1465,7 +1461,7 @@ bool InterpreterThread::pop_stack(Value& out_thread_result) auto res_v = ::std::move(this->m_stack.back().ret); this->m_stack.pop_back(); - + if( this->m_stack.empty() ) { LOG_DEBUG("Thread complete, result " << res_v); @@ -1539,8 +1535,7 @@ bool InterpreterThread::call_path(Value& ret, const ::HIR::Path& path, ::std::ve { if( path == ::HIR::SimplePath { "std", { "sys", "imp", "c", "SetThreadStackGuarantee" } } ) { - ret = Value(::HIR::TypeRef{RawType::I32}); - ret.write_i32(0, 120); // ERROR_CALL_NOT_IMPLEMENTED + ret = Value::new_i32(120); //ERROR_CALL_NOT_IMPLEMENTED return true; } @@ -1552,7 +1547,7 @@ bool InterpreterThread::call_path(Value& ret, const ::HIR::Path& path, ::std::ve ret.write_u64(8, 0); return true; } - + // - No stack overflow handling needed if( path == ::HIR::SimplePath { "std", { "sys", "imp", "stack_overflow", "imp", "init" } } ) { @@ -1567,7 +1562,7 @@ bool InterpreterThread::call_path(Value& ret, const ::HIR::Path& path, ::std::ve // External function! return this->call_extern(ret, fcn.external.link_name, fcn.external.link_abi, ::std::move(args)); } - + this->m_stack.push_back(StackFrame(fcn, ::std::move(args))); return false; } @@ -1586,10 +1581,8 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c ::HIR::TypeRef rty { RawType::Unit }; rty.wrappers.push_back({ TypeWrapper::Ty::Pointer, 0 }); - rv = Value(rty); - rv.write_usize(0, 0); // TODO: Use the alignment when making an allocation? - rv.allocation->relocations.push_back({ 0, RelocationPtr::new_alloc(Allocation::new_alloc(size)) }); + rv = Value::new_pointer(rty, 0, RelocationPtr::new_alloc(Allocation::new_alloc(size))); } else if( link_name == "__rust_reallocate" ) { @@ -1632,13 +1625,12 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c auto arg = args.at(1); auto data_ptr = args.at(2).read_pointer_valref_mut(0, POINTER_SIZE); auto vtable_ptr = args.at(3).read_pointer_valref_mut(0, POINTER_SIZE); - + ::std::vector<Value> sub_args; sub_args.push_back( ::std::move(arg) ); this->m_stack.push_back(StackFrame::make_wrapper([=](Value& out_rv, Value /*rv*/)->bool{ - out_rv = Value(::HIR::TypeRef(RawType::U32)); - out_rv.write_u32(0, 0); + out_rv = Value::new_u32(0); return true; })); @@ -1667,8 +1659,7 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c else if( link_name == "AddVectoredExceptionHandler" ) { LOG_DEBUG("Call `AddVectoredExceptionHandler` - Ignoring and returning non-null"); - rv = Value(::HIR::TypeRef(RawType::USize)); - rv.write_usize(0, 1); + rv = Value::new_usize(1); } else if( link_name == "GetModuleHandleW" ) { @@ -1732,8 +1723,7 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c ssize_t val = write(fd, buf, count); - rv = Value(::HIR::TypeRef(RawType::ISize)); - rv.write_isize(0, val); + rv = Value::new_isize(val); } else if( link_name == "sysconf" ) { @@ -1742,33 +1732,27 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c long val = sysconf(name); - rv = Value(::HIR::TypeRef(RawType::USize)); - rv.write_usize(0, val); + rv = Value::new_usize(val); } else if( link_name == "pthread_mutex_init" || link_name == "pthread_mutex_lock" || link_name == "pthread_mutex_unlock" || link_name == "pthread_mutex_destroy" ) { - rv = Value(::HIR::TypeRef(RawType::I32)); - rv.write_i32(0, 0); + rv = Value::new_i32(0); } else if( link_name == "pthread_rwlock_rdlock" ) { - rv = Value(::HIR::TypeRef(RawType::I32)); - rv.write_i32(0, 0); + rv = Value::new_i32(0); } else if( link_name == "pthread_mutexattr_init" || link_name == "pthread_mutexattr_settype" || link_name == "pthread_mutexattr_destroy" ) { - rv = Value(::HIR::TypeRef(RawType::I32)); - rv.write_i32(0, 0); + rv = Value::new_i32(0); } else if( link_name == "pthread_condattr_init" || link_name == "pthread_condattr_destroy" || link_name == "pthread_condattr_setclock" ) { - rv = Value(::HIR::TypeRef(RawType::I32)); - rv.write_i32(0, 0); + rv = Value::new_i32(0); } else if( link_name == "pthread_cond_init" || link_name == "pthread_cond_destroy" ) { - rv = Value(::HIR::TypeRef(RawType::I32)); - rv.write_i32(0, 0); + rv = Value::new_i32(0); } else if( link_name == "pthread_key_create" ) { @@ -1776,9 +1760,8 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c auto key = ThreadState::s_next_tls_key ++; key_ref.m_alloc.alloc().write_u32( key_ref.m_offset, key ); - - rv = Value(::HIR::TypeRef(RawType::I32)); - rv.write_i32(0, 0); + + rv = Value::new_i32(0); } else if( link_name == "pthread_getspecific" ) { @@ -1787,8 +1770,7 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c // Get a pointer-sized value from storage uint64_t v = key < m_thread.tls_values.size() ? m_thread.tls_values[key] : 0; - rv = Value(::HIR::TypeRef(RawType::USize)); - rv.write_usize(0, v); + rv = Value::new_usize(v); } else if( link_name == "pthread_setspecific" ) { @@ -1801,13 +1783,11 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c } m_thread.tls_values[key] = v; - rv = Value(::HIR::TypeRef(RawType::I32)); - rv.write_i32(0, 0); + rv = Value::new_i32(0); } else if( link_name == "pthread_key_delete" ) { - rv = Value(::HIR::TypeRef(RawType::I32)); - rv.write_i32(0, 0); + rv = Value::new_i32(0); } #endif // std C diff --git a/tools/standalone_miri/value.cpp b/tools/standalone_miri/value.cpp index c3db284a..c1098d1d 100644 --- a/tools/standalone_miri/value.cpp +++ b/tools/standalone_miri/value.cpp @@ -499,6 +499,11 @@ void Allocation::write_bytes(size_t ofs, const void* src, size_t count) ::std::memcpy(this->data_ptr() + ofs, src, count); mark_bytes_valid(ofs, count); } +void Allocation::write_ptr(size_t ofs, size_t ptr_ofs, RelocationPtr reloc) +{ + this->write_usize(ofs, ptr_ofs); + this->relocations.push_back(Relocation { ofs, /*POINTER_SIZE,*/ ::std::move(reloc) }); +} ::std::ostream& operator<<(::std::ostream& os, const Allocation& x) { auto flags = os.flags(); @@ -620,6 +625,34 @@ Value Value::new_ffiptr(FFIPointer ffi) rv.allocation->mask.at(0) = 0xFF; // TODO: Get pointer size and make that much valid instead of 8 bytes return rv; } +Value Value::new_pointer(::HIR::TypeRef ty, uint64_t v, RelocationPtr r) { + assert(!ty.wrappers.empty()); + assert(ty.wrappers[0].type == TypeWrapper::Ty::Borrow || ty.wrappers[0].type == TypeWrapper::Ty::Pointer); + Value rv(ty); + rv.write_usize(0, v); + rv.allocation->relocations.push_back(Relocation { 0, /*POINTER_SIZE,*/ ::std::move(r) }); + return rv; +} +Value Value::new_usize(uint64_t v) { + Value rv( ::HIR::TypeRef(RawType::USize) ); + rv.write_usize(0, v); + return rv; +} +Value Value::new_isize(int64_t v) { + Value rv( ::HIR::TypeRef(RawType::ISize) ); + rv.write_isize(0, v); + return rv; +} +Value Value::new_u32(uint32_t v) { + Value rv( ::HIR::TypeRef(RawType::U32) ); + rv.write_u32(0, v); + return rv; +} +Value Value::new_i32(int32_t v) { + Value rv( ::HIR::TypeRef(RawType::I32) ); + rv.write_i32(0, v); + return rv; +} void Value::create_allocation() { @@ -775,6 +808,14 @@ void Value::write_value(size_t ofs, Value v) } } } +void Value::write_ptr(size_t ofs, size_t ptr_ofs, RelocationPtr reloc) +{ + if( !this->allocation ) + { + LOG_ERROR("Writing a pointer with no allocation"); + } + this->allocation->write_ptr(ofs, ptr_ofs, ::std::move(reloc)); +} ::std::ostream& operator<<(::std::ostream& os, const Value& v) { diff --git a/tools/standalone_miri/value.hpp b/tools/standalone_miri/value.hpp index 81302e67..b057b3c4 100644 --- a/tools/standalone_miri/value.hpp +++ b/tools/standalone_miri/value.hpp @@ -201,6 +201,7 @@ struct ValueCommonWrite: 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: @@ -245,6 +246,7 @@ public: void write_value(size_t ofs, Value v); 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); @@ -254,7 +256,8 @@ struct Value: // If NULL, data is direct 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; @@ -265,8 +268,11 @@ struct Value: 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_usize(uint64_t v); - //static Value new_isize(int64_t v); + 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->size() : direct_data.size; } @@ -289,7 +295,7 @@ struct Value: void write_value(size_t ofs, Value v); 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); + void write_ptr(size_t ofs, size_t ptr_ofs, RelocationPtr reloc) override; }; extern ::std::ostream& operator<<(::std::ostream& os, const Value& v); |