diff options
Diffstat (limited to 'tools/standalone_miri/miri.cpp')
-rw-r--r-- | tools/standalone_miri/miri.cpp | 1547 |
1 files changed, 1223 insertions, 324 deletions
diff --git a/tools/standalone_miri/miri.cpp b/tools/standalone_miri/miri.cpp index fd4d45d8..4eadac66 100644 --- a/tools/standalone_miri/miri.cpp +++ b/tools/standalone_miri/miri.cpp @@ -5,18 +5,26 @@ * miri.cpp * - Interpreter core */ +#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include "module_tree.hpp" #include "value.hpp" +#include "string_view.hpp" #include <algorithm> #include <iomanip> #include "debug.hpp" #include "miri.hpp" +// VVV FFI #include <cstring> // memrchr +#include <sys/stat.h> +#include <fcntl.h> #ifdef _WIN32 # define NOMINMAX # include <Windows.h> +#else +# include <unistd.h> #endif +#undef DEBUG unsigned ThreadState::s_next_tls_key = 1; @@ -25,6 +33,7 @@ class PrimitiveValue public: virtual ~PrimitiveValue() {} + virtual bool is_zero() const = 0; virtual bool add(const PrimitiveValue& v) = 0; virtual bool subtract(const PrimitiveValue& v) = 0; virtual bool multiply(const PrimitiveValue& v) = 0; @@ -50,6 +59,9 @@ struct PrimitiveUInt: PrimitiveUInt(T v): v(v) {} ~PrimitiveUInt() override {} + virtual bool is_zero() const { + return this->v == 0; + } bool add(const PrimitiveValue& x) override { const auto* xp = &x.check<Self>("add"); T newv = this->v + xp->v; @@ -100,6 +112,20 @@ struct PrimitiveU32: public PrimitiveUInt<uint32_t> tgt.write_u32(ofs, this->v); } }; +struct PrimitiveU16: public PrimitiveUInt<uint16_t> +{ + PrimitiveU16(uint16_t v): PrimitiveUInt(v) {} + void write_to_value(ValueCommonWrite& tgt, size_t ofs) const override { + tgt.write_u16(ofs, this->v); + } +}; +struct PrimitiveU8: public PrimitiveUInt<uint8_t> +{ + PrimitiveU8(uint8_t v): PrimitiveUInt(v) {} + void write_to_value(ValueCommonWrite& tgt, size_t ofs) const override { + tgt.write_u8(ofs, this->v); + } +}; template<typename T> struct PrimitiveSInt: public PrimitiveValue @@ -110,6 +136,9 @@ struct PrimitiveSInt: PrimitiveSInt(T v): v(v) {} ~PrimitiveSInt() override {} + virtual bool is_zero() const { + return this->v == 0; + } // TODO: Make this correct. bool add(const PrimitiveValue& x) override { const auto* xp = &x.check<Self>("add"); @@ -179,6 +208,12 @@ public: LOG_ASSERT(t.get_wrapper() == nullptr, "PrimitiveValueVirt::from_value: " << t); switch(t.inner_type) { + case RawType::U8: + new(&rv.buf) PrimitiveU8(v.read_u8(0)); + break; + case RawType::U16: + new(&rv.buf) PrimitiveU16(v.read_u16(0)); + break; case RawType::U32: new(&rv.buf) PrimitiveU32(v.read_u32(0)); break; @@ -254,142 +289,166 @@ struct MirHelpers { } - ValueRef get_value_and_type(const ::MIR::LValue& lv, ::HIR::TypeRef& ty) + ValueRef get_value_and_type_root(const ::MIR::LValue::Storage& lv_root, ::HIR::TypeRef& ty) { - switch(lv.tag()) + switch(lv_root.tag()) { - case ::MIR::LValue::TAGDEAD: throw ""; + case ::MIR::LValue::Storage::TAGDEAD: throw ""; // --> Slots - TU_ARM(lv, Return, _e) { - ty = this->frame.fcn.ret_ty; + TU_ARM(lv_root, Return, _e) { + ty = this->frame.fcn->ret_ty; return ValueRef(this->frame.ret); } break; - TU_ARM(lv, Local, e) { - ty = this->frame.fcn.m_mir.locals.at(e); + TU_ARM(lv_root, Local, e) { + ty = this->frame.fcn->m_mir.locals.at(e); return ValueRef(this->frame.locals.at(e)); } break; - TU_ARM(lv, Argument, e) { - ty = this->frame.fcn.args.at(e.idx); - return ValueRef(this->frame.args.at(e.idx)); + TU_ARM(lv_root, Argument, e) { + ty = this->frame.fcn->args.at(e); + return ValueRef(this->frame.args.at(e)); } break; - TU_ARM(lv, Static, e) { + TU_ARM(lv_root, Static, e) { /*const*/ auto& s = this->thread.m_modtree.get_static(e); ty = s.ty; return ValueRef(s.val); } break; - // --> Modifiers - TU_ARM(lv, Index, e) { - auto idx = get_value_ref(*e.idx).read_usize(0); - ::HIR::TypeRef array_ty; - auto base_val = get_value_and_type(*e.val, array_ty); - const auto* wrapper = array_ty.get_wrapper(); - if( !wrapper ) - { - LOG_ERROR("Indexing non-array/slice - " << array_ty); - } - else if( wrapper->type == TypeWrapper::Ty::Array ) + } + throw ""; + } + ValueRef get_value_and_type(const ::MIR::LValue& lv, ::HIR::TypeRef& ty) + { + auto vr = get_value_and_type_root(lv.m_root, ty); + for(const auto& w : lv.m_wrappers) + { + switch(w.tag()) { - ty = array_ty.get_inner(); - // Check index against array size - if( idx >= wrapper->size ) { - LOG_ERROR("Index out of bounds on array " << array_ty << ", idx=" << idx); + case ::MIR::LValue::Wrapper::TAGDEAD: throw ""; + // --> Modifiers + TU_ARM(w, Index, idx_var) { + auto idx = this->frame.locals.at(idx_var).read_usize(0); + const auto* wrapper = ty.get_wrapper(); + if( !wrapper ) + { + LOG_ERROR("Indexing non-array/slice - " << ty); throw "ERROR"; } - base_val.m_offset += static_cast<size_t>(ty.get_size() * idx); - return base_val; - } - else if( wrapper->type == TypeWrapper::Ty::Slice ) - { - LOG_TODO("Slice index"); - } - else - { - LOG_ERROR("Indexing non-array/slice - " << array_ty); - throw "ERROR"; - } - } break; - TU_ARM(lv, Field, e) { - ::HIR::TypeRef composite_ty; - auto base_val = get_value_and_type(*e.val, composite_ty); - // TODO: if there's metadata present in the base, but the inner doesn't have metadata, clear the metadata - size_t inner_ofs; - ty = composite_ty.get_field(e.field_index, inner_ofs); - LOG_DEBUG("Field - " << composite_ty << "#" << e.field_index << " = @" << inner_ofs << " " << ty); - base_val.m_offset += inner_ofs; - if( ty.get_meta_type() == HIR::TypeRef(RawType::Unreachable) ) - { - LOG_ASSERT(base_val.m_size >= ty.get_size(), "Field didn't fit in the value - " << ty.get_size() << " required, but " << base_val.m_size << " avail"); - base_val.m_size = ty.get_size(); - } - return base_val; - } - TU_ARM(lv, Downcast, e) { - ::HIR::TypeRef composite_ty; - auto base_val = get_value_and_type(*e.val, composite_ty); - LOG_DEBUG("Downcast - " << composite_ty); - - size_t inner_ofs; - ty = composite_ty.get_field(e.variant_index, inner_ofs); - base_val.m_offset += inner_ofs; - return base_val; - } - TU_ARM(lv, Deref, e) { - ::HIR::TypeRef ptr_ty; - auto val = get_value_and_type(*e.val, ptr_ty); - ty = ptr_ty.get_inner(); - LOG_DEBUG("val = " << val << ", (inner) ty=" << ty); - - LOG_ASSERT(val.m_size >= POINTER_SIZE, "Deref of a value that doesn't fit a pointer - " << ty); - size_t ofs = static_cast<size_t>( val.read_usize(0) ); // TODO: Limits? - - // There MUST be a relocation at this point with a valid allocation. - auto alloc = val.get_relocation(val.m_offset); - LOG_TRACE("Deref " << alloc << " + " << ofs << " to give value of type " << ty); - // NOTE: No alloc can happen when dereferencing a zero-sized pointer - if( alloc.is_alloc() ) - { - LOG_DEBUG("> " << lv << " alloc=" << alloc.alloc()); - } - size_t size; + else if( wrapper->type == TypeWrapper::Ty::Array ) + { + ty = ty.get_inner(); + vr.m_offset += ty.get_size() * idx; + } + else if( wrapper->type == TypeWrapper::Ty::Slice ) + { + ty = ty.get_inner(); + LOG_ASSERT(vr.m_metadata, "No slice metadata"); + auto len = vr.m_metadata->read_usize(0); + LOG_ASSERT(idx < len, "Slice index out of range"); + vr.m_offset += ty.get_size() * idx; + vr.m_metadata.reset(); + } + else + { + LOG_ERROR("Indexing non-array/slice - " << ty); + throw "ERROR"; + } + } break; + TU_ARM(w, Field, fld_idx) { + // TODO: if there's metadata present in the base, but the inner doesn't have metadata, clear the metadata + size_t inner_ofs; + auto inner_ty = ty.get_field(fld_idx, inner_ofs); + LOG_DEBUG("Field - " << ty << "#" << fld_idx << " = @" << inner_ofs << " " << inner_ty); + vr.m_offset += inner_ofs; + if( inner_ty.get_meta_type() == HIR::TypeRef(RawType::Unreachable) ) + { + LOG_ASSERT(vr.m_size >= inner_ty.get_size(), "Field didn't fit in the value - " << inner_ty.get_size() << " required, but " << vr.m_size << " available"); + vr.m_size = inner_ty.get_size(); + } + ty = ::std::move(inner_ty); + } + TU_ARM(w, Downcast, variant_index) { + auto composite_ty = ::std::move(ty); + LOG_DEBUG("Downcast - " << composite_ty); - const auto meta_ty = ty.get_meta_type(); - ::std::shared_ptr<Value> meta_val; - // If the type has metadata, store it. - if( meta_ty != RawType::Unreachable ) - { - auto meta_size = meta_ty.get_size(); - 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) ); + size_t inner_ofs; + ty = composite_ty.get_field(variant_index, inner_ofs); + vr.m_offset += inner_ofs; + } + TU_ARM(w, Deref, _) { + auto ptr_ty = ::std::move(ty); + ty = ptr_ty.get_inner(); + LOG_DEBUG("Deref - " << vr << " into " << ty); + + LOG_ASSERT(vr.m_size >= POINTER_SIZE, "Deref pointer isn't large enough to be a pointer"); + // TODO: Move the metadata machinery into `deref` (or at least the logic needed to get the value size) + //auto inner_val = vr.deref(0, ty); + size_t ofs = vr.read_usize(0); + LOG_ASSERT(ofs != 0, "Dereferencing NULL pointer"); + auto alloc = vr.get_relocation(0); + if( alloc ) + { + // TODO: It's valid to dereference (but not read) a non-null invalid pointer. + LOG_ASSERT(ofs >= Allocation::PTR_BASE, "Dereferencing invalid pointer - " << ofs << " into " << alloc); + ofs -= Allocation::PTR_BASE; + } + else + { + } - size_t slice_inner_size; - if( ty.has_slice_meta(slice_inner_size) ) { - size = (ty.get_wrapper() == nullptr ? ty.get_size() : 0) + static_cast<size_t>(meta_val->read_usize(0)) * slice_inner_size; + // There MUST be a relocation at this point with a valid allocation. + LOG_TRACE("Interpret " << alloc << " + " << ofs << " as value of type " << ty); + // NOTE: No alloc can happen when dereferencing a zero-sized pointer + if( alloc.is_alloc() ) + { + //LOG_DEBUG("Deref - lvr=" << ::MIR::LValue::CRef(lv, &w - &lv.m_wrappers.front()) << " alloc=" << alloc.alloc()); } - //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 + { + LOG_ASSERT(ty.get_meta_type() != RawType::Unreachable || ty.get_size() >= 0, "Dereference (giving a non-ZST) with no allocation"); } - } - else - { - LOG_ASSERT(val.m_size == POINTER_SIZE, "Deref of a value that isn't a pointer-sized value (size=" << val.m_size << ") - " << val << ": " << ptr_ty); - size = ty.get_size(); - if( !alloc ) { - LOG_ERROR("Deref of a value with no relocation - " << val); + size_t size; + + const auto meta_ty = ty.get_meta_type(); + ::std::shared_ptr<Value> meta_val; + // If the type has metadata, store it. + if( meta_ty != RawType::Unreachable ) + { + auto meta_size = meta_ty.get_size(); + LOG_ASSERT(vr.m_size == POINTER_SIZE + meta_size, "Deref of " << ty << ", but pointer isn't correct size"); + meta_val = ::std::make_shared<Value>( vr.read_value(POINTER_SIZE, meta_size) ); + + size_t slice_inner_size; + if( ty.has_slice_meta(slice_inner_size) ) { + // Slice metadata, add the base size (if it's a struct) to the variable size + // - `get_wrapper` will return non-null for `[T]`, special-case `str` + size = (ty != RawType::Str && ty.get_wrapper() == nullptr ? 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); + // TODO: if the inner type is a trait object, then check that it has an allocation. + size = alloc.get_size() - ofs; + } + } + else + { + LOG_DEBUG("sizeof(" << ty << ") = " << ty.get_size()); + LOG_ASSERT(vr.m_size == POINTER_SIZE, "Deref of a value that isn't a pointer-sized value (size=" << vr << ") - " << vr << ": " << ptr_ty); + size = ty.get_size(); + if( !alloc && size > 0 ) { + LOG_ERROR("Deref of a non-ZST pointer with no relocation - " << vr); + } } - } - LOG_DEBUG("alloc=" << alloc << ", ofs=" << ofs << ", size=" << size); - auto rv = ValueRef(::std::move(alloc), ofs, size); - rv.m_metadata = ::std::move(meta_val); - return rv; - } break; + LOG_DEBUG("Deref - New VR: alloc=" << alloc << ", ofs=" << ofs << ", size=" << size); + vr = ValueRef(::std::move(alloc), ofs, size); + vr.m_metadata = ::std::move(meta_val); + } break; + } } - throw ""; + return vr; } ValueRef get_value_ref(const ::MIR::LValue& lv) { @@ -422,11 +481,14 @@ struct MirHelpers ::HIR::TypeRef ty; auto base_value = get_value_and_type(lv, ty); - if(base_value.m_alloc) { - base_value.m_alloc.alloc().write_value(base_value.m_offset, ::std::move(val)); - } - else { - base_value.m_value->write_value(base_value.m_offset, ::std::move(val)); + if( val.size() > 0 ) + { + if(!base_value.m_value) { + base_value.m_alloc.alloc().write_value(base_value.m_offset, ::std::move(val)); + } + else { + base_value.m_value->write_value(base_value.m_offset, ::std::move(val)); + } } } @@ -447,7 +509,11 @@ struct MirHelpers 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 + // i128/u128 need the upper bytes cleared+valid + if( ce.t.raw_type == RawType::U128 ) { + uint64_t zero = 0; + val.write_bytes(8, &zero, 8); + } return val; } break; TU_ARM(c, Bool, ce) { @@ -474,12 +540,17 @@ struct MirHelpers LOG_BUG("Constant::Const in mmir"); } break; TU_ARM(c, Bytes, ce) { - LOG_TODO("Constant::Bytes"); + ty = ::HIR::TypeRef(RawType::U8).wrap(TypeWrapper::Ty::Slice, 0).wrap(TypeWrapper::Ty::Borrow, 0); + Value val = Value(ty); + val.write_ptr(0, Allocation::PTR_BASE + 0, RelocationPtr::new_ffi(FFIPointer::new_const_bytes("Constant::Bytes", ce.data(), ce.size()))); + val.write_usize(POINTER_SIZE, ce.size()); + LOG_DEBUG(c << " = " << val); + return val; } break; TU_ARM(c, StaticString, ce) { ty = ::HIR::TypeRef(RawType::Str).wrap(TypeWrapper::Ty::Borrow, 0); Value val = Value(ty); - val.write_ptr(0, 0, RelocationPtr::new_string(&ce)); + val.write_ptr(0, Allocation::PTR_BASE + 0, RelocationPtr::new_string(&ce)); val.write_usize(POINTER_SIZE, ce.size()); LOG_DEBUG(c << " = " << val); return val; @@ -487,15 +558,16 @@ struct MirHelpers // --> Accessor TU_ARM(c, ItemAddr, ce) { // Create a value with a special backing allocation of zero size that references the specified item. - if( /*const auto* fn =*/ this->thread.m_modtree.get_function_opt(ce) ) { + if( /*const auto* fn =*/ this->thread.m_modtree.get_function_opt(*ce) ) { ty = ::HIR::TypeRef(RawType::Function); - return Value::new_fnptr(ce); + return Value::new_fnptr(*ce); } - if( const auto* s = this->thread.m_modtree.get_static_opt(ce) ) { + if( const auto* s = this->thread.m_modtree.get_static_opt(*ce) ) { ty = s->ty.wrapped(TypeWrapper::Ty::Borrow, 0); - return Value::new_pointer(ty, 0, RelocationPtr::new_alloc(s->val.allocation)); + LOG_ASSERT(s->val.m_inner.is_alloc, "Statics should already have an allocation assigned"); + return Value::new_pointer(ty, Allocation::PTR_BASE + 0, RelocationPtr::new_alloc(s->val.m_inner.alloc.alloc)); } - LOG_ERROR("Constant::ItemAddr - " << ce << " - not found"); + LOG_ERROR("Constant::ItemAddr - " << *ce << " - not found"); } break; } throw ""; @@ -546,15 +618,15 @@ InterpreterThread::~InterpreterThread() for(size_t i = 0; i < m_stack.size(); i++) { const auto& frame = m_stack[m_stack.size() - 1 - i]; - ::std::cout << "#" << i << ": "; + ::std::cout << "#" << i << ": F" << frame.frame_index << " "; if( frame.cb ) { ::std::cout << "WRAPPER"; } else { - ::std::cout << frame.fcn.my_path << " BB" << frame.bb_idx << "/"; - if( frame.stmt_idx == frame.fcn.m_mir.blocks.at(frame.bb_idx).statements.size() ) + ::std::cout << frame.fcn->my_path << " BB" << frame.bb_idx << "/"; + if( frame.stmt_idx == frame.fcn->m_mir.blocks.at(frame.bb_idx).statements.size() ) ::std::cout << "TERM"; else ::std::cout << frame.stmt_idx; @@ -576,10 +648,11 @@ bool InterpreterThread::step_one(Value& out_thread_result) assert( !this->m_stack.empty() ); assert( !this->m_stack.back().cb ); auto& cur_frame = this->m_stack.back(); - TRACE_FUNCTION_R(cur_frame.fcn.my_path, ""); - const auto& bb = cur_frame.fcn.m_mir.blocks.at( cur_frame.bb_idx ); + auto instr_idx = this->m_instruction_count++; + TRACE_FUNCTION_R("#" << instr_idx << " " << cur_frame.fcn->my_path << " BB" << cur_frame.bb_idx << "/" << cur_frame.stmt_idx, "#" << instr_idx); + const auto& bb = cur_frame.fcn->m_mir.blocks.at( cur_frame.bb_idx ); - const size_t MAX_STACK_DEPTH = 40; + const size_t MAX_STACK_DEPTH = 90; if( this->m_stack.size() > MAX_STACK_DEPTH ) { LOG_ERROR("Maximum stack depth of " << MAX_STACK_DEPTH << " exceeded"); @@ -590,7 +663,7 @@ bool InterpreterThread::step_one(Value& out_thread_result) if( cur_frame.stmt_idx < bb.statements.size() ) { const auto& stmt = bb.statements[cur_frame.stmt_idx]; - LOG_DEBUG("=== BB" << cur_frame.bb_idx << "/" << cur_frame.stmt_idx << ": " << stmt); + LOG_DEBUG("=== F" << cur_frame.frame_index << " BB" << cur_frame.bb_idx << "/" << cur_frame.stmt_idx << ": " << stmt); switch(stmt.tag()) { case ::MIR::Statement::TAGDEAD: throw ""; @@ -609,25 +682,24 @@ bool InterpreterThread::step_one(Value& out_thread_result) ::HIR::TypeRef src_ty; ValueRef src_base_value = state.get_value_and_type(re.val, src_ty); auto alloc = src_base_value.m_alloc; + // If the source doesn't yet have a relocation, give it a backing allocation so we can borrow if( !alloc && src_base_value.m_value ) { - if( !src_base_value.m_value->allocation ) - { - src_base_value.m_value->create_allocation(); - } - alloc = RelocationPtr::new_alloc( src_base_value.m_value->allocation ); + LOG_DEBUG("Borrow - Creating allocation for " << src_base_value); + alloc = RelocationPtr::new_alloc( src_base_value.m_value->borrow("Borrow") ); } if( alloc.is_alloc() ) - LOG_DEBUG("- alloc=" << alloc << " (" << alloc.alloc() << ")"); + LOG_DEBUG("Borrow - alloc=" << alloc << " (" << alloc.alloc() << ")"); else - LOG_DEBUG("- alloc=" << alloc); + LOG_DEBUG("Borrow - alloc=" << alloc); size_t ofs = src_base_value.m_offset; const auto meta = src_ty.get_meta_type(); auto dst_ty = src_ty.wrapped(TypeWrapper::Ty::Borrow, static_cast<size_t>(re.type)); + LOG_DEBUG("Borrow - ofs=" << ofs << ", meta_ty=" << meta); - // Create the pointer + // Create the pointer (can this just store into the target?) new_val = Value(dst_ty); - new_val.write_ptr(0, ofs, ::std::move(alloc)); + new_val.write_ptr(0, Allocation::PTR_BASE + ofs, ::std::move(alloc)); // - Add metadata if required if( meta != RawType::Unreachable ) { @@ -687,13 +759,22 @@ bool InterpreterThread::step_one(Value& out_thread_result) else if( const auto* src_w = src_ty.get_wrapper() ) { if( src_w->type != TypeWrapper::Ty::Pointer && src_w->type != TypeWrapper::Ty::Borrow ) { - LOG_ERROR("Attempting to cast to a non-pointer - " << src_ty); + LOG_ERROR("Attempting to cast from a non-pointer - " << src_ty); } // TODO: MUST be a thin pointer? // TODO: MUST be an integer (usize only?) - if( re.type != RawType::USize && re.type != RawType::ISize ) { - LOG_ERROR("Casting from a pointer to non-usize - " << re.type << " to " << src_ty); + switch(re.type.wrappers.empty() ? re.type.inner_type : RawType::Unreachable) + { + case RawType::USize: + case RawType::ISize: + break; + case RawType::U64: + case RawType::I64: + // TODO: Only if 64-bit? + break; + default: + LOG_ERROR("Casting from a pointer to non-usize - " << src_ty << " to " << re.type); throw "ERROR"; } new_val = src_value.read_value(0, re.type.get_size()); @@ -726,8 +807,8 @@ bool InterpreterThread::step_one(Value& out_thread_result) case RawType::Bool: throw "ERROR"; case RawType::F32: throw "BUG"; case RawType::F64: dst_val = static_cast<float>( src_value.read_f64(0) ); break; - case RawType::USize: throw "TODO";// /*dst_val = src_value.read_usize();*/ break; - case RawType::ISize: throw "TODO";// /*dst_val = src_value.read_isize();*/ break; + case RawType::USize: LOG_TODO("f32 from " << src_ty);// /*dst_val = src_value.read_usize();*/ break; + case RawType::ISize: LOG_TODO("f32 from " << src_ty);// /*dst_val = src_value.read_isize();*/ break; case RawType::U8: dst_val = static_cast<float>( src_value.read_u8 (0) ); break; case RawType::I8: dst_val = static_cast<float>( src_value.read_i8 (0) ); break; case RawType::U16: dst_val = static_cast<float>( src_value.read_u16(0) ); break; @@ -736,8 +817,8 @@ bool InterpreterThread::step_one(Value& out_thread_result) case RawType::I32: dst_val = static_cast<float>( src_value.read_i32(0) ); break; case RawType::U64: dst_val = static_cast<float>( src_value.read_u64(0) ); break; case RawType::I64: dst_val = static_cast<float>( src_value.read_i64(0) ); break; - case RawType::U128: throw "TODO";// /*dst_val = src_value.read_u128();*/ break; - case RawType::I128: throw "TODO";// /*dst_val = src_value.read_i128();*/ break; + case RawType::U128: LOG_TODO("f32 from " << src_ty);// /*dst_val = src_value.read_u128();*/ break; + case RawType::I128: LOG_TODO("f32 from " << src_ty);// /*dst_val = src_value.read_i128();*/ break; } new_val.write_f32(0, dst_val); } break; @@ -774,7 +855,15 @@ bool InterpreterThread::step_one(Value& out_thread_result) case RawType::Bool: LOG_TODO("Cast to " << re.type); case RawType::Char: - LOG_TODO("Cast to " << re.type); + switch(src_ty.inner_type) + { + case RawType::Char: new_val.write_u32(0, src_value.read_u32(0) ); break; + case RawType::U8: new_val.write_u32(0, src_value.read_u8(0) ); break; + default: + LOG_ERROR("Cast from " << src_ty << " to char isn't valid"); + break; + } + break; case RawType::USize: case RawType::U8: case RawType::U16: @@ -799,14 +888,30 @@ bool InterpreterThread::step_one(Value& out_thread_result) LOG_ASSERT(re.type.inner_type == RawType::USize, "Function pointers can only be casted to usize, instead " << re.type); new_val = src_value.read_value(0, re.type.get_size()); break; - case RawType::Char: - LOG_ASSERT(re.type.inner_type == RawType::U32, "Char can only be casted to u32, instead " << re.type); - new_val = src_value.read_value(0, 4); - break; + case RawType::Char: { + uint32_t v = src_value.read_u32(0); + switch(re.type.inner_type) + { + case RawType::U8: + if( v > 0xFF ) { + LOG_NOTICE("Casting to u8 from char above 255"); + } + new_val.write_u8(0, v & 0xFF); + break; + case RawType::U32: + new_val = src_value.read_value(0, 4); + break; + case RawType::USize: + new_val.write_usize(0, v); + break; + default: + LOG_ERROR("Char can only be casted to u32/u8, instead " << re.type); + } + } break; case RawType::Unit: LOG_FATAL("Cast of unit"); case RawType::Composite: { - const auto& dt = *src_ty.composite_type; + const auto& dt = src_ty.composite_type(); if( dt.variants.size() == 0 ) { LOG_FATAL("Cast of composite - " << src_ty); } @@ -893,6 +998,13 @@ bool InterpreterThread::step_one(Value& out_thread_result) if(0) case RawType::I64: dst_val = static_cast<uint64_t>( src_value.read_i64(0) ); + if(0) + case RawType::U128: + dst_val = static_cast<uint64_t>( src_value.read_u128(0) ); + if(0) + case RawType::I128: + LOG_TODO("Cast i128 to " << re.type); + //dst_val = static_cast<uint64_t>( src_value.read_i128(0) ); switch(re.type.inner_type) { @@ -930,13 +1042,20 @@ bool InterpreterThread::step_one(Value& out_thread_result) throw ""; } break; - case RawType::U128: throw "TODO"; /*dst_val = src_value.read_u128();*/ break; - case RawType::I128: throw "TODO"; /*dst_val = src_value.read_i128();*/ break; } } break; case RawType::U128: - case RawType::I128: - LOG_TODO("Cast to " << re.type); + case RawType::I128: { + U128 dst_val; + switch(src_ty.inner_type) + { + case RawType::U8: dst_val = src_value.read_u8 (0); break; + case RawType::I8: dst_val = src_value.read_i8 (0); break; + default: + LOG_TODO("Cast " << src_ty << " to " << re.type); + } + new_val.write_u128(0, dst_val); + } break; } } } break; @@ -957,18 +1076,65 @@ bool InterpreterThread::step_one(Value& out_thread_result) case ::MIR::eBinOp::LE: { LOG_ASSERT(ty_l == ty_r, "BinOp type mismatch - " << ty_l << " != " << ty_r); int res = 0; - // TODO: Handle comparison of the relocations too - //const auto& alloc_l = v_l.m_value ? v_l.m_value->allocation : v_l.m_alloc; - //const auto& alloc_r = v_r.m_value ? v_r.m_value->allocation : v_r.m_alloc; - auto reloc_l = /*alloc_l ? */v_l.get_relocation(v_l.m_offset)/* : RelocationPtr()*/; - auto reloc_r = /*alloc_r ? */v_r.get_relocation(v_r.m_offset)/* : RelocationPtr()*/; + auto reloc_l = v_l.get_relocation(0); + auto reloc_r = v_r.get_relocation(0); - if( reloc_l != reloc_r ) + + // TODO: Handle comparison of the relocations too + // - If both sides have a relocation: + // > EQ/NE always valid + // > others require the same relocation + // - If one side has a relocation: + // > EQ/NE only allow zero on the non-reloc side + // > others are invalid? + if( reloc_l && reloc_r ) { - res = (reloc_l < reloc_r ? -1 : 1); + // Both have relocations, check if they're equal + if( reloc_l != reloc_r ) + { + switch(re.op) + { + case ::MIR::eBinOp::EQ: + case ::MIR::eBinOp::NE: + res = 1; + break; + default: + LOG_FATAL("Unable to compare " << v_l << " and " << v_r << " - different relocations (" << reloc_l << " != " << reloc_r << ")"); + } + // - Equality will always fail + // - Ordering is a bug + } + else + { + // Equal: Allow all comparisons + } + } + else if( reloc_l || reloc_r ) + { + // Only one side + // - Ordering is a bug + // - Equalities are allowed, but only for `0`? + // > TODO: If the side with no reloation doesn't have value `0` then error? + switch(re.op) + { + case ::MIR::eBinOp::EQ: + case ::MIR::eBinOp::NE: + // - Allow success, as addresses can be masked down + break; + default: + if( reloc_l ) + res = 1; + else// if( reloc_r ) + res = -1; + //LOG_FATAL("Unable to order " << v_l << " and " << v_r << " - different relocations"); + break; + } + } + else + { + // No relocations, no need to check more } - LOG_DEBUG("res=" << res << ", " << reloc_l << " ? " << reloc_r); if( const auto* w = ty_l.get_wrapper() ) { @@ -1010,6 +1176,10 @@ bool InterpreterThread::step_one(Value& out_thread_result) case RawType::I8 : res = res != 0 ? res : Ops::do_compare(v_l.read_i8 (0), v_r.read_i8 (0)); break; case RawType::USize: res = res != 0 ? res : Ops::do_compare(v_l.read_usize(0), v_r.read_usize(0)); break; case RawType::ISize: res = res != 0 ? res : Ops::do_compare(v_l.read_isize(0), v_r.read_isize(0)); break; + case RawType::Char: res = res != 0 ? res : Ops::do_compare(v_l.read_u32(0), v_r.read_u32(0)); break; + case RawType::Bool: res = res != 0 ? res : Ops::do_compare(v_l.read_u8(0), v_r.read_u8(0)); break; // TODO: `read_bool` that checks for bool values? + case RawType::U128: res = res != 0 ? res : Ops::do_compare(v_l.read_u128(0), v_r.read_u128(0)); break; + case RawType::I128: res = res != 0 ? res : Ops::do_compare(v_l.read_i128(0), v_r.read_i128(0)); break; default: LOG_TODO("BinOp comparisons - " << se.src << " w/ " << ty_l); } @@ -1034,36 +1204,40 @@ bool InterpreterThread::step_one(Value& out_thread_result) case ::MIR::eBinOp::BIT_SHR: { LOG_ASSERT(ty_l.get_wrapper() == nullptr, "Bitwise operator on non-primitive - " << ty_l); LOG_ASSERT(ty_r.get_wrapper() == nullptr, "Bitwise operator with non-primitive - " << ty_r); - size_t max_bits = ty_r.get_size() * 8; + size_t max_bits = ty_l.get_size() * 8; uint8_t shift; - auto check_cast = [&](uint64_t v){ LOG_ASSERT(0 <= v && v <= static_cast<decltype(v)>(max_bits), "Shift out of range - " << v); return static_cast<uint8_t>(v); }; + auto check_cast_u = [&](auto v){ LOG_ASSERT(0 <= v && v <= max_bits, "Shift out of range - " << v); return static_cast<uint8_t>(v); }; + auto check_cast_s = [&](auto v){ LOG_ASSERT(v <= static_cast<int64_t>(max_bits), "Shift out of range - " << v); return static_cast<uint8_t>(v); }; switch(ty_r.inner_type) { - case RawType::U64: shift = check_cast(v_r.read_u64(0)); break; - case RawType::U32: shift = check_cast(v_r.read_u32(0)); break; - case RawType::U16: shift = check_cast(v_r.read_u16(0)); break; - case RawType::U8 : shift = check_cast(v_r.read_u8 (0)); break; - case RawType::I64: shift = check_cast(v_r.read_i64(0)); break; - case RawType::I32: shift = check_cast(v_r.read_i32(0)); break; - case RawType::I16: shift = check_cast(v_r.read_i16(0)); break; - case RawType::I8 : shift = check_cast(v_r.read_i8 (0)); break; - case RawType::USize: shift = check_cast(v_r.read_usize(0)); break; - case RawType::ISize: shift = check_cast(v_r.read_isize(0)); break; + case RawType::U64: shift = check_cast_u(v_r.read_u64(0)); break; + case RawType::U32: shift = check_cast_u(v_r.read_u32(0)); break; + case RawType::U16: shift = check_cast_u(v_r.read_u16(0)); break; + case RawType::U8 : shift = check_cast_u(v_r.read_u8 (0)); break; + case RawType::I64: shift = check_cast_s(v_r.read_i64(0)); break; + case RawType::I32: shift = check_cast_s(v_r.read_i32(0)); break; + case RawType::I16: shift = check_cast_s(v_r.read_i16(0)); break; + case RawType::I8 : shift = check_cast_s(v_r.read_i8 (0)); break; + case RawType::USize: shift = check_cast_u(v_r.read_usize(0)); break; + case RawType::ISize: shift = check_cast_s(v_r.read_isize(0)); break; default: - LOG_TODO("BinOp shift rhs unknown type - " << se.src << " w/ " << ty_r); + LOG_TODO("BinOp shift RHS unknown type - " << se.src << " w/ " << ty_r); } new_val = Value(ty_l); switch(ty_l.inner_type) { // TODO: U128 + case RawType::U128: new_val.write_u128(0, Ops::do_bitwise(v_l.read_u128(0), U128(shift), re.op)); break; case RawType::U64: new_val.write_u64(0, Ops::do_bitwise(v_l.read_u64(0), static_cast<uint64_t>(shift), re.op)); break; case RawType::U32: new_val.write_u32(0, Ops::do_bitwise(v_l.read_u32(0), static_cast<uint32_t>(shift), re.op)); break; case RawType::U16: new_val.write_u16(0, Ops::do_bitwise(v_l.read_u16(0), static_cast<uint16_t>(shift), re.op)); break; case RawType::U8 : new_val.write_u8 (0, Ops::do_bitwise(v_l.read_u8 (0), static_cast<uint8_t >(shift), re.op)); break; case RawType::USize: new_val.write_usize(0, Ops::do_bitwise(v_l.read_usize(0), static_cast<uint64_t>(shift), re.op)); break; - // TODO: Is signed allowed? + // Is signed allowed? (yes) + // - What's the exact semantics? For now assuming it's unsigned+reinterpret + case RawType::ISize: new_val.write_usize(0, Ops::do_bitwise(v_l.read_usize(0), static_cast<uint64_t>(shift), re.op)); break; default: - LOG_TODO("BinOp shift rhs unknown type - " << se.src << " w/ " << ty_r); + LOG_TODO("BinOp shift LHS unknown type - " << se.src << " w/ " << ty_l); } } break; case ::MIR::eBinOp::BIT_AND: @@ -1074,7 +1248,10 @@ bool InterpreterThread::step_one(Value& out_thread_result) new_val = Value(ty_l); switch(ty_l.inner_type) { - // TODO: U128/I128 + case RawType::U128: + case RawType::I128: + new_val.write_u128( 0, Ops::do_bitwise(v_l.read_u128(0), v_r.read_u128(0), re.op) ); + break; case RawType::U64: case RawType::I64: new_val.write_u64( 0, Ops::do_bitwise(v_l.read_u64(0), v_r.read_u64(0), re.op) ); @@ -1089,6 +1266,7 @@ bool InterpreterThread::step_one(Value& out_thread_result) break; case RawType::U8: case RawType::I8: + case RawType::Bool: new_val.write_u8 ( 0, static_cast<uint8_t >(Ops::do_bitwise(v_l.read_u8 (0), v_r.read_u8 (0), re.op)) ); break; case RawType::USize: @@ -1098,16 +1276,44 @@ bool InterpreterThread::step_one(Value& out_thread_result) default: LOG_TODO("BinOp bitwise - " << se.src << " w/ " << ty_l); } + // If the LHS had a relocation, propagate it over + if( auto r = v_l.get_relocation(0) ) + { + LOG_DEBUG("- Restore relocation " << r); + new_val.set_reloc(0, ::std::min(POINTER_SIZE, new_val.size()), r); + } break; default: LOG_ASSERT(ty_l == ty_r, "BinOp type mismatch - " << ty_l << " != " << ty_r); auto val_l = PrimitiveValueVirt::from_value(ty_l, v_l); auto val_r = PrimitiveValueVirt::from_value(ty_r, v_r); + RelocationPtr new_val_reloc; switch(re.op) { - case ::MIR::eBinOp::ADD: val_l.get().add( val_r.get() ); break; - case ::MIR::eBinOp::SUB: val_l.get().subtract( val_r.get() ); break; + case ::MIR::eBinOp::ADD: + LOG_ASSERT(!v_r.get_relocation(0), "RHS of `+` has a relocation"); + new_val_reloc = v_l.get_relocation(0); + val_l.get().add( val_r.get() ); + break; + case ::MIR::eBinOp::SUB: + if( auto r = v_l.get_relocation(0) ) + { + if( v_r.get_relocation(0) ) + { + // Pointer difference, no relocation in output + } + else + { + new_val_reloc = ::std::move(r); + } + } + else + { + LOG_ASSERT(!v_r.get_relocation(0), "RHS of `-` has a relocation but LHS does not"); + } + val_l.get().subtract( val_r.get() ); + break; case ::MIR::eBinOp::MUL: val_l.get().multiply( val_r.get() ); break; case ::MIR::eBinOp::DIV: val_l.get().divide( val_r.get() ); break; case ::MIR::eBinOp::MOD: val_l.get().modulo( val_r.get() ); break; @@ -1117,6 +1323,10 @@ bool InterpreterThread::step_one(Value& out_thread_result) } new_val = Value(ty_l); val_l.get().write_to_value(new_val, 0); + if( new_val_reloc ) + { + new_val.set_reloc(0, ::std::min(POINTER_SIZE, new_val.size()), ::std::move(new_val_reloc)); + } break; } } break; @@ -1216,10 +1426,18 @@ bool InterpreterThread::step_one(Value& out_thread_result) state.get_value_and_type(se.dst, dst_ty); new_val = Value(dst_ty); - for(size_t i = 0; i < re.vals.size(); i++) + if( dst_ty.inner_type == RawType::Unit ) { - auto fld_ofs = dst_ty.composite_type->fields.at(i).first; - new_val.write_value(fld_ofs, state.param_to_value(re.vals[i])); + LOG_ASSERT(re.vals.size() == 0 , ""); + } + else + { + LOG_ASSERT(dst_ty.inner_type == RawType::Composite, dst_ty); + for(size_t i = 0; i < re.vals.size(); i++) + { + auto fld_ofs = dst_ty.composite_type().fields.at(i).first; + new_val.write_value(fld_ofs, state.param_to_value(re.vals[i])); + } } } break; TU_ARM(se.src, Array, re) { @@ -1287,12 +1505,15 @@ bool InterpreterThread::step_one(Value& out_thread_result) ::HIR::TypeRef dst_ty; state.get_value_and_type(se.dst, dst_ty); new_val = Value(dst_ty); - LOG_ASSERT(dst_ty.composite_type == &data_ty, "Destination type of RValue::Struct isn't the same as the input"); + LOG_ASSERT(dst_ty.inner_type == RawType::Composite, dst_ty); + LOG_ASSERT(dst_ty.ptr.composite_type == &data_ty, "Destination type of RValue::Struct isn't the same as the input"); for(size_t i = 0; i < re.vals.size(); i++) { auto fld_ofs = data_ty.fields.at(i).first; - new_val.write_value(fld_ofs, state.param_to_value(re.vals[i])); + auto v = state.param_to_value(re.vals[i]); + LOG_DEBUG("Struct - @" << fld_ofs << " = " << v); + new_val.write_value(fld_ofs, ::std::move(v)); } } break; } @@ -1309,21 +1530,17 @@ bool InterpreterThread::step_one(Value& out_thread_result) auto v = state.get_value_and_type(se.slot, ty); // - Take a pointer to the inner - auto alloc = v.m_alloc; - if( !alloc ) - { - if( !v.m_value->allocation ) - { - v.m_value->create_allocation(); - } - alloc = RelocationPtr::new_alloc( v.m_value->allocation ); - } + auto alloc = (v.m_value ? RelocationPtr::new_alloc(v.m_value->borrow("drop")) : v.m_alloc); size_t ofs = v.m_offset; - assert(ty.get_meta_type() == RawType::Unreachable); + //LOG_ASSERT(ty.get_meta_type() == RawType::Unreachable, "Dropping an unsized type with Statement::Drop - " << ty); - auto ptr_ty = ty.wrapped(TypeWrapper::Ty::Borrow, 2); + auto ptr_ty = ty.wrapped(TypeWrapper::Ty::Borrow, /*BorrowTy::Unique*/2); - auto ptr_val = Value::new_pointer(ptr_ty, ofs, ::std::move(alloc)); + auto ptr_val = Value::new_pointer(ptr_ty, Allocation::PTR_BASE + ofs, ::std::move(alloc)); + if( v.m_metadata ) + { + ptr_val.write_value(POINTER_SIZE, *v.m_metadata); + } if( !drop_value(ptr_val, ty, /*shallow=*/se.kind == ::MIR::eDropKind::SHALLOW) ) { @@ -1345,14 +1562,17 @@ bool InterpreterThread::step_one(Value& out_thread_result) } else { - LOG_DEBUG("=== BB" << cur_frame.bb_idx << "/TERM: " << bb.terminator); + LOG_DEBUG("=== F" << cur_frame.frame_index << " BB" << cur_frame.bb_idx << "/TERM: " << bb.terminator); switch(bb.terminator.tag()) { case ::MIR::Terminator::TAGDEAD: throw ""; TU_ARM(bb.terminator, Incomplete, _te) LOG_TODO("Terminator::Incomplete hit"); TU_ARM(bb.terminator, Diverge, _te) - LOG_TODO("Terminator::Diverge hit"); + LOG_DEBUG("DIVERGE (continue panic)"); + assert(m_thread.panic_count > 0); + m_thread.panic_active = true; + return this->pop_stack(out_thread_result); TU_ARM(bb.terminator, Panic, _te) LOG_TODO("Terminator::Panic"); TU_ARM(bb.terminator, Goto, te) @@ -1371,13 +1591,14 @@ bool InterpreterThread::step_one(Value& out_thread_result) auto v = state.get_value_and_type(te.val, ty); LOG_ASSERT(ty.get_wrapper() == nullptr, "Matching on wrapped value - " << ty); LOG_ASSERT(ty.inner_type == RawType::Composite, "Matching on non-coposite - " << ty); + LOG_DEBUG("Switch v = " << v); // TODO: Convert the variant list into something that makes it easier to switch on. size_t found_target = SIZE_MAX; size_t default_target = SIZE_MAX; - for(size_t i = 0; i < ty.composite_type->variants.size(); i ++) + for(size_t i = 0; i < ty.composite_type().variants.size(); i ++) { - const auto& var = ty.composite_type->variants[i]; + const auto& var = ty.composite_type().variants[i]; if( var.tag_data.size() == 0 ) { // Save as the default, error for multiple defaults @@ -1399,6 +1620,7 @@ bool InterpreterThread::step_one(Value& out_thread_result) continue ; if( ::std::memcmp(tmp.data(), var.tag_data.data(), tmp.size()) == 0 ) { + LOG_DEBUG("Explicit match " << i); found_target = i; break ; } @@ -1407,6 +1629,7 @@ bool InterpreterThread::step_one(Value& out_thread_result) if( found_target == SIZE_MAX ) { + LOG_DEBUG("Default match " << default_target); found_target = default_target; } if( found_target == SIZE_MAX ) @@ -1415,8 +1638,93 @@ bool InterpreterThread::step_one(Value& out_thread_result) } cur_frame.bb_idx = te.targets.at(found_target); } break; - TU_ARM(bb.terminator, SwitchValue, _te) - LOG_TODO("Terminator::SwitchValue"); + TU_ARM(bb.terminator, SwitchValue, te) { + ::HIR::TypeRef ty; + auto v = state.get_value_and_type(te.val, ty); + TU_MATCH_HDRA( (te.values), {) + TU_ARMA(Unsigned, vals) { + LOG_ASSERT(vals.size() == te.targets.size(), "Mismatch in SwitchValue target/value list lengths"); + // Read an unsigned value + if( ty.get_wrapper() ) { + LOG_ERROR("Terminator::SwitchValue::Unsigned with wrapped type - " << ty); + } + uint64_t switch_val; + switch(ty.inner_type) + { + case RawType::U8: switch_val = v.read_u8(0); break; + case RawType::U16: switch_val = v.read_u16(0); break; + case RawType::U32: switch_val = v.read_u32(0); break; + case RawType::U64: switch_val = v.read_u64(0); break; + case RawType::U128: LOG_TODO("Terminator::SwitchValue::Unsigned with u128"); + case RawType::USize: switch_val = v.read_usize(0); break; + case RawType::Char: switch_val = v.read_u32(0); break; + default: + LOG_ERROR("Terminator::SwitchValue::Unsigned with unexpected type - " << ty); + } + + auto it = ::std::find(vals.begin(), vals.end(), switch_val); + if( it != vals.end() ) + { + auto idx = it - vals.begin(); + LOG_TRACE("- " << switch_val << " matched arm " << idx); + cur_frame.bb_idx = te.targets.at(idx); + } + else + { + LOG_TRACE("- " << switch_val << " not matched, taking default arm"); + cur_frame.bb_idx = te.def_target; + } + } + TU_ARMA(Signed, vals) { + if( ty.get_wrapper() ) { + LOG_ERROR("Terminator::SwitchValue::Signed with wrapped type - " << ty); + } + int64_t switch_val; + switch(ty.inner_type) + { + case RawType::I8: switch_val = v.read_i8(0); break; + case RawType::I16: switch_val = v.read_i16(0); break; + case RawType::I32: switch_val = v.read_i32(0); break; + case RawType::I64: switch_val = v.read_i64(0); break; + case RawType::I128: LOG_TODO("Terminator::SwitchValue::Signed with i128"); + case RawType::ISize: switch_val = v.read_isize(0); break; + default: + LOG_ERROR("Terminator::SwitchValue::Signed with unexpected type - " << ty); + } + + auto it = ::std::find(vals.begin(), vals.end(), switch_val); + if( it != vals.end() ) + { + auto idx = it - vals.begin(); + LOG_TRACE("- " << switch_val << " matched arm " << idx); + cur_frame.bb_idx = te.targets.at(idx); + } + else + { + LOG_TRACE("- " << switch_val << " not matched, taking default arm"); + cur_frame.bb_idx = te.def_target; + } + } + TU_ARMA(String, vals) { + auto size = v.read_usize(POINTER_SIZE); + const char* sv_ptr = reinterpret_cast<const char*>(v.read_pointer_const(0, size)); + auto switch_val = ::stdx::string_view(sv_ptr, sv_ptr+size); + + auto it = ::std::find_if(vals.begin(), vals.end(), [&](const ::std::string& x){ return switch_val == x; }); + if( it != vals.end() ) + { + auto idx = it - vals.begin(); + LOG_TRACE("- '" << switch_val << "' matched arm " << idx); + cur_frame.bb_idx = te.targets.at(idx); + } + else + { + LOG_TRACE("- '" << switch_val << "' not matched, taking default arm"); + cur_frame.bb_idx = te.def_target; + } + } + } + } TU_ARM(bb.terminator, Call, te) { ::std::vector<Value> sub_args; sub_args.reserve(te.args.size()); for(const auto& a : te.args) @@ -1447,10 +1755,9 @@ bool InterpreterThread::step_one(Value& out_thread_result) LOG_DEBUG("> Indirect call " << v); // TODO: Assert type // TODO: Assert offset/content. - assert(v.read_usize(0) == 0); - fcn_alloc_ptr = v.get_relocation(v.m_offset); - if( !fcn_alloc_ptr ) - LOG_FATAL("Calling value with no relocation - " << v); + LOG_ASSERT(v.read_usize(0) == Allocation::PTR_BASE, "Function pointer value invalid - " << v); + fcn_alloc_ptr = v.get_relocation(0); + LOG_ASSERT(fcn_alloc_ptr, "Calling value with no relocation - " << v); LOG_ASSERT(fcn_alloc_ptr.get_ty() == RelocationPtr::Ty::Function, "Calling value that isn't a function pointer"); fcn_p = &fcn_alloc_ptr.fcn(); } @@ -1459,12 +1766,23 @@ bool InterpreterThread::step_one(Value& out_thread_result) if( !this->call_path(rv, *fcn_p, ::std::move(sub_args)) ) { // Early return, don't want to update stmt_idx yet + LOG_DEBUG("- Non-immediate return, do not advance yet"); return false; } } - LOG_DEBUG(te.ret_val << " = " << rv << " (resume " << cur_frame.fcn.my_path << ")"); - state.write_lvalue(te.ret_val, rv); - cur_frame.bb_idx = te.ret_block; + // If a panic is in progress (in thread state), take the panic block instead + if( m_thread.panic_active ) + { + m_thread.panic_active = false; + LOG_DEBUG("Panic into " << cur_frame.fcn->my_path); + cur_frame.bb_idx = te.panic_block; + } + else + { + LOG_DEBUG(te.ret_val << " = " << rv << " (resume " << cur_frame.fcn->my_path << ")"); + state.write_lvalue(te.ret_val, rv); + cur_frame.bb_idx = te.ret_block; + } } break; } cur_frame.stmt_idx = 0; @@ -1502,41 +1820,56 @@ bool InterpreterThread::pop_stack(Value& out_thread_result) auto& cur_frame = this->m_stack.back(); MirHelpers state { *this, cur_frame }; - const auto& blk = cur_frame.fcn.m_mir.blocks.at( cur_frame.bb_idx ); + const auto& blk = cur_frame.fcn->m_mir.blocks.at( cur_frame.bb_idx ); if( cur_frame.stmt_idx < blk.statements.size() ) { assert( blk.statements[cur_frame.stmt_idx].is_Drop() ); cur_frame.stmt_idx ++; - LOG_DEBUG("DROP complete (resume " << cur_frame.fcn.my_path << ")"); + LOG_DEBUG("DROP complete (resume " << cur_frame.fcn->my_path << ")"); } else { assert( blk.terminator.is_Call() ); const auto& te = blk.terminator.as_Call(); - LOG_DEBUG(te.ret_val << " = " << res_v << " (resume " << cur_frame.fcn.my_path << ")"); + LOG_DEBUG("Resume " << cur_frame.fcn->my_path); + LOG_DEBUG("F" << cur_frame.frame_index << " " << te.ret_val << " = " << res_v); - state.write_lvalue(te.ret_val, res_v); cur_frame.stmt_idx = 0; - cur_frame.bb_idx = te.ret_block; + // If a panic is in progress (in thread state), take the panic block instead + if( m_thread.panic_active ) + { + m_thread.panic_active = false; + LOG_DEBUG("Panic into " << cur_frame.fcn->my_path); + cur_frame.bb_idx = te.panic_block; + } + else + { + state.write_lvalue(te.ret_val, res_v); + cur_frame.bb_idx = te.ret_block; + } } return false; } } +unsigned InterpreterThread::StackFrame::s_next_frame_index = 0; InterpreterThread::StackFrame::StackFrame(const Function& fcn, ::std::vector<Value> args): - fcn(fcn), - ret( fcn.ret_ty ), + frame_index(s_next_frame_index++), + fcn(&fcn), + ret( fcn.ret_ty == RawType::Unreachable ? Value() : Value(fcn.ret_ty) ), args( ::std::move(args) ), locals( ), drop_flags( fcn.m_mir.drop_flags ), bb_idx(0), stmt_idx(0) { + LOG_DEBUG("F" << frame_index << " - Initializing " << fcn.m_mir.locals.size() << " locals"); this->locals.reserve( fcn.m_mir.locals.size() ); for(const auto& ty : fcn.m_mir.locals) { + LOG_DEBUG("_" << (&ty - &fcn.m_mir.locals.front()) << ": " << ty); if( ty == RawType::Unreachable ) { // HACK: Locals can be !, but they can NEVER be accessed this->locals.push_back( Value() ); @@ -1557,7 +1890,9 @@ bool InterpreterThread::call_path(Value& ret, const ::HIR::Path& path, ::std::ve } // - No guard page needed - if( path == ::HIR::SimplePath { "std", {"sys", "imp", "thread", "guard", "init" } } ) + if( path == ::HIR::SimplePath { "std", {"sys", "imp", "thread", "guard", "init" } } + || path == ::HIR::SimplePath { "std", {"sys", "unix", "thread", "guard", "init" } } + ) { ret = Value::with_size(16, false); ret.write_u64(0, 0); @@ -1576,8 +1911,17 @@ bool InterpreterThread::call_path(Value& ret, const ::HIR::Path& path, ::std::ve if( fcn.external.link_name != "" ) { - // External function! - return this->call_extern(ret, fcn.external.link_name, fcn.external.link_abi, ::std::move(args)); + // TODO: Search for a function with both code and this link name + if(const auto* ext_fcn = m_modtree.get_ext_function(fcn.external.link_name.c_str())) + { + this->m_stack.push_back(StackFrame(*ext_fcn, ::std::move(args))); + return false; + } + else + { + // 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))); @@ -1603,42 +1947,73 @@ extern "C" { #endif bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, const ::std::string& abi, ::std::vector<Value> args) { - if( link_name == "__rust_allocate" ) + struct FfiHelpers { + static const char* read_cstr(const Value& v, size_t ptr_ofs, size_t* out_strlen=nullptr) + { + bool _is_mut; + size_t size; + // Get the base pointer and allocation size (checking for at least one valid byte to start with) + const char* ptr = reinterpret_cast<const char*>( v.read_pointer_unsafe(0, 1, /*out->*/ size, _is_mut) ); + size_t len = 0; + // Seek until either out of space, or a NUL is found + while(size -- && *ptr) + { + ptr ++; + len ++; + } + if( out_strlen ) + { + *out_strlen = len; + } + return reinterpret_cast<const char*>(v.read_pointer_const(0, len + 1)); // Final read will trigger an error if the NUL isn't there + } + }; + if( link_name == "__rust_allocate" || link_name == "__rust_alloc" || link_name == "__rust_alloc_zeroed" ) { + static unsigned s_alloc_count = 0; + + auto alloc_idx = s_alloc_count ++; + auto alloc_name = FMT_STRING("__rust_alloc#" << alloc_idx); auto size = args.at(0).read_usize(0); auto align = args.at(1).read_usize(0); - LOG_DEBUG("__rust_allocate(size=" << size << ", align=" << align << ")"); - auto rty = ::HIR::TypeRef(RawType::Unit).wrap( TypeWrapper::Ty::Pointer, 0 ); + LOG_DEBUG(link_name << "(size=" << size << ", align=" << align << "): name=" << alloc_name); // TODO: Use the alignment when making an allocation? - rv = Value::new_pointer(rty, 0, RelocationPtr::new_alloc(Allocation::new_alloc(size))); + auto alloc = Allocation::new_alloc(size, ::std::move(alloc_name)); + LOG_TRACE("- alloc=" << alloc << " (" << alloc->size() << " bytes)"); + auto rty = ::HIR::TypeRef(RawType::Unit).wrap( TypeWrapper::Ty::Pointer, 0 ); + + if( link_name == "__rust_alloc_zeroed" ) + { + alloc->mark_bytes_valid(0, size); + } + + rv = Value::new_pointer(rty, Allocation::PTR_BASE, RelocationPtr::new_alloc(::std::move(alloc))); } - else if( link_name == "__rust_reallocate" ) + else if( link_name == "__rust_reallocate" || link_name == "__rust_realloc" ) { - LOG_ASSERT(args.at(0).allocation, "__rust_reallocate first argument doesn't have an allocation"); auto alloc_ptr = args.at(0).get_relocation(0); auto ptr_ofs = args.at(0).read_usize(0); - LOG_ASSERT(ptr_ofs == 0, "__rust_reallocate with offset pointer"); auto oldsize = args.at(1).read_usize(0); - auto newsize = args.at(2).read_usize(0); - auto align = args.at(3).read_usize(0); + // NOTE: The ordering here depends on the rust version (1.19 has: old, new, align - 1.29 has: old, align, new) + auto align = args.at(true /*1.29*/ ? 2 : 3).read_usize(0); + auto newsize = args.at(true /*1.29*/ ? 3 : 2).read_usize(0); LOG_DEBUG("__rust_reallocate(ptr=" << alloc_ptr << ", oldsize=" << oldsize << ", newsize=" << newsize << ", align=" << align << ")"); + LOG_ASSERT(ptr_ofs == Allocation::PTR_BASE, "__rust_reallocate with offset pointer"); LOG_ASSERT(alloc_ptr, "__rust_reallocate with no backing allocation attached to pointer"); LOG_ASSERT(alloc_ptr.is_alloc(), "__rust_reallocate with no backing allocation attached to pointer"); auto& alloc = alloc_ptr.alloc(); // TODO: Check old size and alignment against allocation. - alloc.data.resize( (newsize + 8-1) / 8 ); - alloc.mask.resize( (newsize + 8-1) / 8 ); + alloc.resize(newsize); // TODO: Should this instead make a new allocation to catch use-after-free? rv = ::std::move(args.at(0)); } - else if( link_name == "__rust_deallocate" ) + else if( link_name == "__rust_deallocate" || link_name == "__rust_dealloc" ) { - LOG_ASSERT(args.at(0).allocation, "__rust_deallocate first argument doesn't have an allocation"); auto alloc_ptr = args.at(0).get_relocation(0); auto ptr_ofs = args.at(0).read_usize(0); - LOG_ASSERT(ptr_ofs == 0, "__rust_deallocate with offset pointer"); + LOG_ASSERT(ptr_ofs == Allocation::PTR_BASE, "__rust_deallocate with offset pointer"); LOG_DEBUG("__rust_deallocate(ptr=" << alloc_ptr << ")"); LOG_ASSERT(alloc_ptr, "__rust_deallocate with no backing allocation attached to pointer"); @@ -1675,6 +2050,10 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c return false; } } + else if( link_name == "panic_impl" ) + { + LOG_TODO("panic_impl"); + } else if( link_name == "__rust_start_panic" ) { LOG_TODO("__rust_start_panic"); @@ -1683,6 +2062,19 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c { LOG_TODO("rust_begin_unwind"); } + // libunwind + else if( link_name == "_Unwind_RaiseException" ) + { + LOG_DEBUG("_Unwind_RaiseException(" << args.at(0) << ")"); + // Save the first argument in TLS, then return a status that indicates unwinding should commence. + m_thread.panic_active = true; + m_thread.panic_count += 1; + m_thread.panic_value = ::std::move(args.at(0)); + } + else if( link_name == "_Unwind_DeleteException" ) + { + LOG_DEBUG("_Unwind_DeleteException(" << args.at(0) << ")"); + } #ifdef _WIN32 // WinAPI functions used by libstd else if( link_name == "AddVectoredExceptionHandler" ) @@ -1692,7 +2084,6 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c } else if( link_name == "GetModuleHandleW" ) { - LOG_ASSERT(args.at(0).allocation, ""); const auto& tgt_alloc = args.at(0).get_relocation(0); const void* arg0 = (tgt_alloc ? tgt_alloc.alloc().data_ptr() : nullptr); //extern void* GetModuleHandleW(const void* s); @@ -1706,7 +2097,7 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c auto ret = GetModuleHandleW(static_cast<LPCWSTR>(arg0)); if(ret) { - rv = Value::new_ffiptr(FFIPointer { "GetModuleHandleW", ret, 0 }); + rv = Value::new_ffiptr(FFIPointer::new_void("GetModuleHandleW", ret)); } else { @@ -1731,7 +2122,7 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c if( ret ) { - rv = Value::new_ffiptr(FFIPointer { "GetProcAddress", ret, 0 }); + rv = Value::new_ffiptr(FFIPointer::new_void("GetProcAddress", ret)); } else { @@ -1752,6 +2143,96 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c rv = Value::new_isize(val); } + else if( link_name == "read" ) + { + auto fd = args.at(0).read_i32(0); + auto count = args.at(2).read_isize(0); + auto buf_vr = args.at(1).read_pointer_valref_mut(0, count); + + LOG_DEBUG("read(" << fd << ", " << buf_vr.data_ptr_mut() << ", " << count << ")"); + ssize_t val = read(fd, buf_vr.data_ptr_mut(), count); + LOG_DEBUG("= " << val); + + if( val > 0 ) + { + buf_vr.mark_bytes_valid(0, val); + } + + rv = Value::new_isize(val); + } + else if( link_name == "close" ) + { + auto fd = args.at(0).read_i32(0); + LOG_DEBUG("close(" << fd << ")"); + // TODO: Ensure that this FD is from the set known by the FFI layer + close(fd); + } + else if( link_name == "isatty" ) + { + auto fd = args.at(0).read_i32(0); + LOG_DEBUG("isatty(" << fd << ")"); + int rv_i = isatty(fd); + LOG_DEBUG("= " << rv_i); + rv = Value::new_i32(rv_i); + } + else if( link_name == "fcntl" ) + { + // `fcntl` has custom handling for the third argument, as some are pointers + int fd = args.at(0).read_i32(0); + int command = args.at(1).read_i32(0); + + int rv_i; + const char* name; + switch(command) + { + // - No argument + case F_GETFD: name = "F_GETFD"; if(0) + ; + { + LOG_DEBUG("fcntl(" << fd << ", " << name << ")"); + rv_i = fcntl(fd, command); + } break; + // - Integer arguments + case F_DUPFD: name = "F_DUPFD"; if(0) + case F_DUPFD_CLOEXEC: name = "F_DUPFD_CLOEXEC"; if(0) + case F_SETFD: name = "F_SETFD"; if(0) + ; + { + int arg = args.at(2).read_i32(0); + LOG_DEBUG("fcntl(" << fd << ", " << name << ", " << arg << ")"); + rv_i = fcntl(fd, command, arg); + } break; + default: + if( args.size() > 2 ) + { + LOG_TODO("fnctl(..., " << command << ", " << args[2] << ")"); + } + else + { + LOG_TODO("fnctl(..., " << command << ")"); + } + } + + LOG_DEBUG("= " << rv_i); + rv = Value(::HIR::TypeRef(RawType::I32)); + rv.write_i32(0, rv_i); + } + else if( link_name == "prctl" ) + { + auto option = args.at(0).read_i32(0); + int rv_i; + switch(option) + { + case 15: { // PR_SET_NAME - set thread name + auto name = FfiHelpers::read_cstr(args.at(1), 0); + LOG_DEBUG("prctl(PR_SET_NAME, \"" << name << "\""); + rv_i = 0; + } break; + default: + LOG_TODO("prctl(" << option << ", ..."); + } + rv = Value::new_i32(rv_i); + } else if( link_name == "sysconf" ) { auto name = args.at(0).read_i32(0); @@ -1761,6 +2242,10 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c rv = Value::new_usize(val); } + else if( link_name == "pthread_self" ) + { + rv = Value::new_i32(0); + } else if( link_name == "pthread_mutex_init" || link_name == "pthread_mutex_lock" || link_name == "pthread_mutex_unlock" || link_name == "pthread_mutex_destroy" ) { rv = Value::new_i32(0); @@ -1769,6 +2254,11 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c { rv = Value::new_i32(0); } + else if( link_name == "pthread_rwlock_unlock" ) + { + // TODO: Check that this thread holds the lock? + rv = Value::new_i32(0); + } else if( link_name == "pthread_mutexattr_init" || link_name == "pthread_mutexattr_settype" || link_name == "pthread_mutexattr_destroy" ) { rv = Value::new_i32(0); @@ -1777,6 +2267,75 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c { rv = Value::new_i32(0); } + else if( link_name == "pthread_attr_init" || link_name == "pthread_attr_destroy" || link_name == "pthread_getattr_np" ) + { + rv = Value::new_i32(0); + } + else if( link_name == "pthread_attr_setstacksize" ) + { + // Lie and return succeess + rv = Value::new_i32(0); + } + else if( link_name == "pthread_attr_getguardsize" ) + { + const auto attr_p = args.at(0).read_pointer_const(0, 1); + auto out_size = args.at(1).deref(0, HIR::TypeRef(RawType::USize)); + + out_size.m_alloc.alloc().write_usize(out_size.m_offset, 0x1000); + + rv = Value::new_i32(0); + } + else if( link_name == "pthread_attr_getstack" ) + { + const auto attr_p = args.at(0).read_pointer_const(0, 1); + auto out_ptr = args.at(2).deref(0, HIR::TypeRef(RawType::USize)); + auto out_size = args.at(2).deref(0, HIR::TypeRef(RawType::USize)); + + out_size.m_alloc.alloc().write_usize(out_size.m_offset, 0x4000); + + rv = Value::new_i32(0); + } + else if( link_name == "pthread_create" ) + { + auto thread_handle_out = args.at(0).read_pointer_valref_mut(0, sizeof(pthread_t)); + auto attrs = args.at(1).read_pointer_const(0, sizeof(pthread_attr_t)); + auto fcn_path = args.at(2).get_relocation(0).fcn(); + LOG_ASSERT(args.at(2).read_usize(0) == Allocation::PTR_BASE, ""); + auto arg = args.at(3); + LOG_NOTICE("TODO: pthread_create(" << thread_handle_out << ", " << attrs << ", " << fcn_path << ", " << arg << ")"); + // TODO: Create a new interpreter context with this thread, use co-operative scheduling + // HACK: Just run inline + if( true ) + { + auto tls = ::std::move(m_thread.tls_values); + this->m_stack.push_back(StackFrame::make_wrapper([=](Value& out_rv, Value /*rv*/)mutable ->bool { + out_rv = Value::new_i32(0); + m_thread.tls_values = ::std::move(tls); + return true; + })); + + // TODO: Catch the panic out of this. + if( this->call_path(rv, fcn_path, { ::std::move(arg) }) ) + { + bool v = this->pop_stack(rv); + assert( v == false ); + return true; + } + else + { + return false; + } + } + else { + //this->m_parent.create_thread(fcn_path, arg); + rv = Value::new_i32(EPERM); + } + } + else if( link_name == "pthread_detach" ) + { + // "detach" - Prevent the need to explitly join a thread + rv = Value::new_i32(0); + } else if( link_name == "pthread_cond_init" || link_name == "pthread_cond_destroy" ) { rv = Value::new_i32(0); @@ -1795,20 +2354,32 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c auto key = args.at(0).read_u32(0); // Get a pointer-sized value from storage - uint64_t v = key < m_thread.tls_values.size() ? m_thread.tls_values[key] : 0; - - rv = Value::new_usize(v); + if( key < m_thread.tls_values.size() ) + { + const auto& e = m_thread.tls_values[key]; + rv = Value::new_usize(e.first); + if( e.second ) + { + rv.set_reloc(0, POINTER_SIZE, e.second); + } + } + else + { + // Return zero until populated + rv = Value::new_usize(0); + } } else if( link_name == "pthread_setspecific" ) { auto key = args.at(0).read_u32(0); auto v = args.at(1).read_u64(0); + auto v_reloc = args.at(1).get_relocation(0); - // Get a pointer-sized value from storage + // Store a pointer-sized value in storage if( key >= m_thread.tls_values.size() ) { m_thread.tls_values.resize(key+1); } - m_thread.tls_values[key] = v; + m_thread.tls_values[key] = ::std::make_pair(v, v_reloc); rv = Value::new_i32(0); } @@ -1816,6 +2387,72 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c { rv = Value::new_i32(0); } + // - Time + else if( link_name == "clock_gettime" ) + { + // int clock_gettime(clockid_t clk_id, struct timespec *tp); + auto clk_id = args.at(0).read_u32(0); + auto tp_vr = args.at(1).read_pointer_valref_mut(0, sizeof(struct timespec)); + + LOG_DEBUG("clock_gettime(" << clk_id << ", " << tp_vr); + int rv_i = clock_gettime(clk_id, reinterpret_cast<struct timespec*>(tp_vr.data_ptr_mut())); + if(rv_i == 0) + tp_vr.mark_bytes_valid(0, tp_vr.m_size); + LOG_DEBUG("= " << rv_i << " (" << tp_vr << ")"); + rv = Value::new_i32(rv_i); + } + // - Linux extensions + else if( link_name == "open64" ) + { + const auto* path = FfiHelpers::read_cstr(args.at(0), 0); + auto flags = args.at(1).read_i32(0); + auto mode = (args.size() > 2 ? args.at(2).read_i32(0) : 0); + + LOG_DEBUG("open64(\"" << path << "\", " << flags << ")"); + int rv_i = open(path, flags, mode); + LOG_DEBUG("= " << rv_i); + + rv = Value(::HIR::TypeRef(RawType::I32)); + rv.write_i32(0, rv_i); + } + else if( link_name == "stat64" ) + { + const auto* path = FfiHelpers::read_cstr(args.at(0), 0); + auto outbuf_vr = args.at(1).read_pointer_valref_mut(0, sizeof(struct stat)); + + LOG_DEBUG("stat64(\"" << path << "\", " << outbuf_vr << ")"); + int rv_i = stat(path, reinterpret_cast<struct stat*>(outbuf_vr.data_ptr_mut())); + LOG_DEBUG("= " << rv_i); + + if( rv_i == 0 ) + { + // TODO: Mark the buffer as valid? + } + + rv = Value(::HIR::TypeRef(RawType::I32)); + rv.write_i32(0, rv_i); + } + else if( link_name == "__errno_location" ) + { + rv = Value::new_ffiptr(FFIPointer::new_const_bytes("errno", &errno, sizeof(errno))); + } + else if( link_name == "syscall" ) + { + auto num = args.at(0).read_u32(0); + + LOG_DEBUG("syscall(" << num << ", ...) - hack return ENOSYS"); + errno = ENOSYS; + rv = Value::new_i64(-1); + } + else if( link_name == "dlsym" ) + { + auto handle = args.at(0).read_usize(0); + const char* name = FfiHelpers::read_cstr(args.at(1), 0); + + LOG_DEBUG("dlsym(0x" << ::std::hex << handle << ", '" << name << "')"); + LOG_NOTICE("dlsym stubbed to zero"); + rv = Value::new_usize(0); + } #endif // std C else if( link_name == "signal" ) @@ -1824,6 +2461,31 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c rv = Value(::HIR::TypeRef(RawType::USize)); rv.write_usize(0, 1); } + else if( link_name == "sigaction" ) + { + rv = Value::new_i32(-1); + } + else if( link_name == "sigaltstack" ) // POSIX: Set alternate signal stack + { + rv = Value::new_i32(-1); + } + else if( link_name == "memcmp" ) + { + auto n = args.at(2).read_usize(0); + int rv_i; + if( n > 0 ) + { + const void* ptr_b = args.at(1).read_pointer_const(0, n); + const void* ptr_a = args.at(0).read_pointer_const(0, n); + + rv_i = memcmp(ptr_a, ptr_b, n); + } + else + { + rv_i = 0; + } + rv = Value::new_i32(rv_i); + } // - `void *memchr(const void *s, int c, size_t n);` else if( link_name == "memchr" ) { @@ -1835,11 +2497,10 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c const void* ret = memchr(ptr, c, n); rv = Value(::HIR::TypeRef(RawType::USize)); - rv.create_allocation(); if( ret ) { - rv.write_usize(0, args.at(0).read_usize(0) + ( static_cast<const uint8_t*>(ret) - static_cast<const uint8_t*>(ptr) )); - rv.allocation->relocations.push_back({ 0, ptr_alloc }); + auto rv_ofs = args.at(0).read_usize(0) + ( static_cast<const uint8_t*>(ret) - static_cast<const uint8_t*>(ptr) ); + rv.write_ptr(0, rv_ofs, ptr_alloc); } else { @@ -1856,11 +2517,10 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c const void* ret = memrchr(ptr, c, n); rv = Value(::HIR::TypeRef(RawType::USize)); - rv.create_allocation(); if( ret ) { - rv.write_usize(0, args.at(0).read_usize(0) + ( static_cast<const uint8_t*>(ret) - static_cast<const uint8_t*>(ptr) )); - rv.allocation->relocations.push_back({ 0, ptr_alloc }); + auto rv_ofs = args.at(0).read_usize(0) + ( static_cast<const uint8_t*>(ret) - static_cast<const uint8_t*>(ptr) ); + rv.write_ptr(0, rv_ofs, ptr_alloc); } else { @@ -1870,21 +2530,35 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c else if( link_name == "strlen" ) { // strlen - custom implementation to ensure validity - bool _is_mut; - size_t size; - const char* ptr = reinterpret_cast<const char*>( args.at(0).read_pointer_unsafe(0, 1, size, _is_mut) ); size_t len = 0; - while(size -- && *ptr) - { - ptr ++; - len ++; - } - args.at(0).read_pointer_const(0, len + 1); + FfiHelpers::read_cstr(args.at(0), 0, &len); //rv = Value::new_usize(len); rv = Value(::HIR::TypeRef(RawType::USize)); rv.write_usize(0, len); } + else if( link_name == "getenv" ) + { + const auto* name = FfiHelpers::read_cstr(args.at(0), 0); + LOG_DEBUG("getenv(\"" << name << "\")"); + const auto* ret_ptr = getenv(name); + if( ret_ptr ) + { + LOG_DEBUG("= \"" << ret_ptr << "\""); + rv = Value::new_ffiptr(FFIPointer::new_const_bytes("getenv", ret_ptr, strlen(ret_ptr)+1)); + } + else + { + LOG_DEBUG("= NULL"); + rv = Value(::HIR::TypeRef(RawType::USize)); + rv.create_allocation(); + rv.write_usize(0,0); + } + } + else if( link_name == "setenv" ) + { + LOG_TODO("Allow `setenv` without incurring thread unsafety"); + } // Allocators! else { @@ -1893,7 +2567,7 @@ bool InterpreterThread::call_extern(Value& rv, const ::std::string& link_name, c return true; } -bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, const ::HIR::PathParams& ty_params, ::std::vector<Value> args) +bool InterpreterThread::call_intrinsic(Value& rv, const RcString& name, const ::HIR::PathParams& ty_params, ::std::vector<Value> args) { TRACE_FUNCTION_R(name, rv); for(const auto& a : args) @@ -1907,15 +2581,69 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con { it = type_ids.insert(it, ty_T); } - + rv = Value::with_size(POINTER_SIZE, false); rv.write_usize(0, it - type_ids.begin()); } + else if( name == "type_name" ) + { + const auto& ty_T = ty_params.tys.at(0); + + static ::std::map<HIR::TypeRef, ::std::string> s_type_names; + auto it = s_type_names.find(ty_T); + if( it == s_type_names.end() ) + { + it = s_type_names.insert( ::std::make_pair(ty_T, FMT_STRING(ty_T)) ).first; + } + + rv = Value::with_size(2*POINTER_SIZE, /*needs_alloc=*/true); + rv.write_ptr(0*POINTER_SIZE, Allocation::PTR_BASE, RelocationPtr::new_string(&it->second)); + rv.write_usize(1*POINTER_SIZE, 0); + } + else if( name == "discriminant_value" ) + { + const auto& ty = ty_params.tys.at(0); + ValueRef val = args.at(0).deref(0, ty); + + size_t fallback = SIZE_MAX; + size_t found_index = SIZE_MAX; + LOG_ASSERT(ty.inner_type == RawType::Composite, "discriminant_value " << ty); + for(size_t i = 0; i < ty.composite_type().variants.size(); i ++) + { + const auto& var = ty.composite_type().variants[i]; + if( var.tag_data.size() == 0 ) + { + // Only seen in Option<NonNull> + assert(fallback == SIZE_MAX); + fallback = i; + } + else + { + // Get offset to the tag + ::HIR::TypeRef tag_ty; + size_t tag_ofs = ty.get_field_ofs(var.base_field, var.field_path, tag_ty); + // Compare + if( val.compare(tag_ofs, var.tag_data.data(), var.tag_data.size()) == 0 ) + { + found_index = i; + break ; + } + } + } + + if( found_index == SIZE_MAX ) + { + LOG_ASSERT(fallback != SIZE_MAX, "Can't find variant of " << ty << " for " << val); + found_index = fallback; + } + + rv = Value::new_usize(found_index); + } else if( name == "atomic_fence" || name == "atomic_fence_acq" ) { rv = Value(); } - else if( name == "atomic_store" ) + else if( name == "atomic_store" || name == "atomic_store_relaxed" || name == "atomic_store_rel" ) { auto& ptr_val = args.at(0); auto& data_val = args.at(1); @@ -1927,20 +2655,20 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con LOG_ASSERT(alloc, "Deref of a value with no relocation"); // TODO: Atomic side of this? - size_t ofs = ptr_val.read_usize(0); + size_t ofs = ptr_val.read_usize(0) - Allocation::PTR_BASE; alloc.alloc().write_value(ofs, ::std::move(data_val)); } - else if( name == "atomic_load" || name == "atomic_load_relaxed" ) + else if( name == "atomic_load" || name == "atomic_load_relaxed" || name == "atomic_load_acq" ) { auto& ptr_val = args.at(0); - LOG_ASSERT(ptr_val.size() == POINTER_SIZE, "atomic_store of a value that isn't a pointer-sized value"); + LOG_ASSERT(ptr_val.size() == POINTER_SIZE, "atomic_load of a value that isn't a pointer-sized value"); // There MUST be a relocation at this point with a valid allocation. auto alloc = ptr_val.get_relocation(0); LOG_ASSERT(alloc, "Deref of a value with no relocation"); // TODO: Atomic lock the allocation. - size_t ofs = ptr_val.read_usize(0); + size_t ofs = ptr_val.read_usize(0) - Allocation::PTR_BASE; const auto& ty = ty_params.tys.at(0); rv = alloc.alloc().read_value(ofs, ty.get_size()); @@ -1948,7 +2676,7 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con else if( name == "atomic_xadd" || name == "atomic_xadd_relaxed" ) { const auto& ty_T = ty_params.tys.at(0); - auto ptr_ofs = args.at(0).read_usize(0); + auto ptr_ofs = args.at(0).read_usize(0) - Allocation::PTR_BASE; auto ptr_alloc = args.at(0).get_relocation(0); auto v = args.at(1).read_value(0, ty_T.get_size()); @@ -1969,7 +2697,7 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con else if( name == "atomic_xsub" || name == "atomic_xsub_relaxed" || name == "atomic_xsub_rel" ) { const auto& ty_T = ty_params.tys.at(0); - auto ptr_ofs = args.at(0).read_usize(0); + auto ptr_ofs = args.at(0).read_usize(0) - Allocation::PTR_BASE; auto ptr_alloc = args.at(0).get_relocation(0); auto v = args.at(1).read_value(0, ty_T.get_size()); @@ -1987,7 +2715,7 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con val_l.get().write_to_value( ptr_alloc.alloc(), ptr_ofs ); } - else if( name == "atomic_xchg" ) + else if( name == "atomic_xchg" || name == "atomic_xchg_acqrel" ) { const auto& ty_T = ty_params.tys.at(0); auto data_ref = args.at(0).read_pointer_valref_mut(0, ty_T.get_size()); @@ -2006,7 +2734,7 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con rv = Value::with_size( ty_T.get_size() + 1, false ); rv.write_value(0, data_ref.read_value(0, old_v.size())); LOG_DEBUG("> *ptr = " << data_ref); - if( data_ref.compare(old_v.data_ptr(), old_v.size()) == true ) { + if( data_ref.compare(0, old_v.data_ptr(), old_v.size()) == true ) { data_ref.m_alloc.alloc().write_value( data_ref.m_offset, new_v ); rv.write_u8( old_v.size(), 1 ); } @@ -2027,6 +2755,27 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con { auto ptr_alloc = args.at(0).get_relocation(0); auto ptr_ofs = args.at(0).read_usize(0); + LOG_ASSERT(ptr_ofs >= Allocation::PTR_BASE, "`offset` with invalid pointer - " << args.at(0)); + auto& ofs_val = args.at(1); + + auto delta_counts = ofs_val.read_usize(0); + auto ty_size = ty_params.tys.at(0).get_size(); + LOG_DEBUG("\"offset\": 0x" << ::std::hex << ptr_ofs << " + 0x" << delta_counts << " * 0x" << ty_size); + ptr_ofs -= Allocation::PTR_BASE; + auto new_ofs = ptr_ofs + delta_counts * ty_size; + if(POINTER_SIZE != 8) { + new_ofs &= 0xFFFFFFFF; + } + + rv = ::std::move(args.at(0)); + rv.write_ptr(0, Allocation::PTR_BASE + new_ofs, ptr_alloc); + } + else if( name == "arith_offset" ) // Doesn't check validity, and allows wrapping + { + auto ptr_alloc = args.at(0).get_relocation(0); + auto ptr_ofs = args.at(0).read_usize(0); + //LOG_ASSERT(ptr_ofs >= Allocation::PTR_BASE, "`offset` with invalid pointer - " << args.at(0)); + //ptr_ofs -= Allocation::PTR_BASE; auto& ofs_val = args.at(1); auto delta_counts = ofs_val.read_usize(0); @@ -2034,11 +2783,16 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con if(POINTER_SIZE != 8) { new_ofs &= 0xFFFFFFFF; } + //new_ofs += Allocation::PTR_BASE; rv = ::std::move(args.at(0)); - rv.write_usize(0, new_ofs); - if( ptr_alloc ) { - rv.allocation->relocations.push_back({ 0, ptr_alloc }); + if( ptr_alloc ) + { + rv.write_ptr(0, new_ofs, ptr_alloc); + } + else + { + rv.write_usize(0, new_ofs); } } // effectively ptr::write @@ -2047,18 +2801,13 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con auto& ptr_val = args.at(0); auto& data_val = args.at(1); - LOG_ASSERT(ptr_val.size() == POINTER_SIZE, "move_val_init of an address that isn't a pointer-sized value"); - // There MUST be a relocation at this point with a valid allocation. - LOG_ASSERT(ptr_val.allocation, "Deref of a value with no allocation (hence no relocations)"); - LOG_TRACE("Deref " << ptr_val << " and store " << data_val); - - auto ptr_alloc = ptr_val.get_relocation(0); - LOG_ASSERT(ptr_alloc, "Deref of a value with no relocation"); + // - TODO: What about FFI? (can't be a string or function though) + auto dst_vr = ptr_val.deref(0, ty_params.tys.at(0)); + LOG_ASSERT(dst_vr.m_alloc, "Deref didn't yeild an allocation (error?)"); + LOG_ASSERT(dst_vr.m_alloc.is_alloc(), "Deref didn't yield an allocation"); - size_t ofs = ptr_val.read_usize(0); - ptr_alloc.alloc().write_value(ofs, ::std::move(data_val)); - LOG_DEBUG(ptr_alloc.alloc()); + dst_vr.m_alloc.alloc().write_value(dst_vr.m_offset, ::std::move(data_val)); } else if( name == "uninit" ) { @@ -2069,6 +2818,22 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con rv = Value(ty_params.tys.at(0)); rv.mark_bytes_valid(0, rv.size()); } + else if( name == "write_bytes" ) + { + auto& dst_ptr_v = args.at(0); + auto byte = args.at(1).read_u8(0); + auto count = args.at(2).read_usize(0); + auto bytes = count * ty_params.tys.at(0).get_size(); + + LOG_DEBUG("'write_bytes'(" << dst_ptr_v << ", " << (int)byte << ", " << count << "): bytes=" << bytes); + + if( count > 0 ) + { + auto dst_vr = dst_ptr_v.read_pointer_valref_mut(0, bytes); + memset(dst_vr.data_ptr_mut(), byte, bytes); + dst_vr.mark_bytes_valid(0, bytes); + } + } // - Unsized stuff else if( name == "size_of_val" ) { @@ -2097,7 +2862,12 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con } else if( ity->inner_type == RawType::TraitObject ) { - LOG_TODO("size_of_val - Trait Object - " << ty); + auto vtable_ty = meta_ty.get_inner(); + LOG_DEBUG("> vtable_ty = " << vtable_ty << " (size= " << vtable_ty.get_size() << ")"); + auto vtable = val.deref(POINTER_SIZE, vtable_ty); + LOG_DEBUG("> vtable = " << vtable); + auto size = vtable.read_usize(1*POINTER_SIZE); + flex_size = size; } else { @@ -2111,12 +2881,86 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con rv.write_usize(0, ty.get_size()); } } + else if( name == "min_align_of_val" ) + { + /*const*/ auto& val = args.at(0); + const auto& ty = ty_params.tys.at(0); + rv = Value(::HIR::TypeRef(RawType::USize)); + size_t fixed_size = 0; // unused + size_t flex_align = 0; + if( const auto* ity = ty.get_unsized_type(fixed_size) ) + { + if( const auto* w = ity->get_wrapper() ) + { + LOG_ASSERT(w->type == TypeWrapper::Ty::Slice, "align_of_val on wrapped type that isn't a slice - " << *ity); + flex_align = ity->get_inner().get_align(); + } + else if( ity->inner_type == RawType::Str ) + { + flex_align = 1; + } + else if( ity->inner_type == RawType::TraitObject ) + { + const auto meta_ty = ty.get_meta_type(); + auto vtable_ty = meta_ty.get_inner(); + LOG_DEBUG("> vtable_ty = " << vtable_ty << " (size= " << vtable_ty.get_size() << ")"); + auto vtable = val.deref(POINTER_SIZE, vtable_ty); + LOG_DEBUG("> vtable = " << vtable); + flex_align = vtable.read_usize(2*POINTER_SIZE); + } + else + { + LOG_BUG("Inner unsized type unknown - " << *ity); + } + } + rv.write_usize(0, ::std::max( ty.get_align(), flex_align )); + } else if( name == "drop_in_place" ) { auto& val = args.at(0); const auto& ty = ty_params.tys.at(0); return drop_value(val, ty); } + else if( name == "try" ) + { + auto fcn_path = args.at(0).get_relocation(0).fcn(); + auto arg = args.at(1); + auto out_panic_value = args.at(2).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*/)mutable->bool{ + if( m_thread.panic_active ) + { + assert(m_thread.panic_count > 0); + m_thread.panic_active = false; + m_thread.panic_count --; + LOG_ASSERT(m_thread.panic_value.size() == out_panic_value.m_size, "Panic value " << m_thread.panic_value << " doesn't fit in " << out_panic_value); + out_panic_value.m_alloc.alloc().write_value( out_panic_value.m_offset, ::std::move(m_thread.panic_value) ); + out_rv = Value::new_u32(1); + return true; + } + else + { + LOG_ASSERT(m_thread.panic_count == 0, "Panic count non-zero, but previous function returned non-panic"); + out_rv = Value::new_u32(0); + return true; + } + })); + + // TODO: Catch the panic out of this. + if( this->call_path(rv, fcn_path, ::std::move(sub_args)) ) + { + bool v = this->pop_stack(rv); + assert( v == false ); + return true; + } + else + { + return false; + } + } // ---------------------------------------------------------------- // Checked arithmatic else if( name == "add_with_overflow" ) @@ -2158,7 +3002,7 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con else if( name == "mul_with_overflow" ) { const auto& ty = ty_params.tys.at(0); - + auto lhs = PrimitiveValueVirt::from_value(ty, args.at(0)); auto rhs = PrimitiveValueVirt::from_value(ty, args.at(1)); bool didnt_overflow = lhs.get().multiply( rhs.get() ); @@ -2173,6 +3017,24 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con lhs.get().write_to_value(rv, dty.fields[0].first); rv.write_u8( dty.fields[1].first, didnt_overflow ? 0 : 1 ); // Returns true if overflow happened } + // - "exact_div" :: Normal divide, but UB if not an exact multiple + else if( name == "exact_div" ) + { + const auto& ty = ty_params.tys.at(0); + + auto lhs = PrimitiveValueVirt::from_value(ty, args.at(0)); + auto rhs = PrimitiveValueVirt::from_value(ty, args.at(1)); + + LOG_ASSERT(!rhs.get().is_zero(), "`exact_div` with zero divisor: " << args.at(0) << " / " << args.at(1)); + auto rem = lhs; + rem.get().modulo( rhs.get() ); + LOG_ASSERT(rem.get().is_zero(), "`exact_div` with yielded non-zero remainder: " << args.at(0) << " / " << args.at(1)); + bool didnt_overflow = lhs.get().divide( rhs.get() ); + LOG_ASSERT(didnt_overflow, "`exact_div` failed for unknown reason: " << args.at(0) << " /" << args.at(1)); + + rv = Value(ty); + lhs.get().write_to_value(rv, 0); + } // Overflowing artithmatic else if( name == "overflowing_sub" ) { @@ -2181,6 +3043,18 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con auto lhs = PrimitiveValueVirt::from_value(ty, args.at(0)); auto rhs = PrimitiveValueVirt::from_value(ty, args.at(1)); lhs.get().subtract( rhs.get() ); + // TODO: Overflowing part + + rv = Value(ty); + lhs.get().write_to_value(rv, 0); + } + else if( name == "overflowing_add" ) + { + const auto& ty = ty_params.tys.at(0); + + auto lhs = PrimitiveValueVirt::from_value(ty, args.at(0)); + auto rhs = PrimitiveValueVirt::from_value(ty, args.at(1)); + lhs.get().add( rhs.get() ); rv = Value(ty); lhs.get().write_to_value(rv, 0); @@ -2189,40 +3063,29 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con // memcpy else if( name == "copy_nonoverlapping" ) { - auto src_ofs = args.at(0).read_usize(0); - auto src_alloc = args.at(0).get_relocation(0); - auto dst_ofs = args.at(1).read_usize(0); - auto dst_alloc = args.at(1).get_relocation(0); + //auto src_ofs = args.at(0).read_usize(0); + //auto src_alloc = args.at(0).get_relocation(0); + //auto dst_ofs = args.at(1).read_usize(0); + //auto dst_alloc = args.at(1).get_relocation(0); size_t ent_count = args.at(2).read_usize(0); size_t ent_size = ty_params.tys.at(0).get_size(); auto byte_count = ent_count * ent_size; + LOG_DEBUG("`copy_nonoverlapping`: byte_count=" << byte_count); - LOG_ASSERT(src_alloc, "Source of copy* must have an allocation"); - LOG_ASSERT(dst_alloc, "Destination of copy* must be a memory allocation"); - LOG_ASSERT(dst_alloc.is_alloc(), "Destination of copy* must be a memory allocation"); - - switch(src_alloc.get_ty()) + // A count of zero doesn't need to do any of the checks (TODO: Validate this rule) + if( byte_count > 0 ) { - case RelocationPtr::Ty::Allocation: { - auto v = src_alloc.alloc().read_value(src_ofs, byte_count); - LOG_DEBUG("v = " << v); - dst_alloc.alloc().write_value(dst_ofs, ::std::move(v)); - } break; - case RelocationPtr::Ty::StdString: - LOG_ASSERT(src_ofs <= src_alloc.str().size(), ""); - LOG_ASSERT(byte_count <= src_alloc.str().size(), ""); - LOG_ASSERT(src_ofs + byte_count <= src_alloc.str().size(), ""); - dst_alloc.alloc().write_bytes(dst_ofs, src_alloc.str().data() + src_ofs, byte_count); - break; - case RelocationPtr::Ty::Function: - LOG_FATAL("Attempt to copy* a function"); - break; - case RelocationPtr::Ty::FfiPointer: - LOG_ASSERT(src_ofs <= src_alloc.ffi().size, ""); - LOG_ASSERT(byte_count <= src_alloc.ffi().size, ""); - LOG_ASSERT(src_ofs + byte_count <= src_alloc.ffi().size, ""); - dst_alloc.alloc().write_bytes(dst_ofs, reinterpret_cast<const char*>(src_alloc.ffi().ptr_value) + src_ofs, byte_count); - break; + auto src_vr = args.at(0).read_pointer_valref_mut(0, byte_count); + auto dst_vr = args.at(1).read_pointer_valref_mut(0, byte_count); + + auto& dst_alloc = dst_vr.m_alloc; + LOG_ASSERT(dst_alloc, "Destination of copy* must be a memory allocation"); + LOG_ASSERT(dst_alloc.is_alloc(), "Destination of copy* must be a memory allocation"); + + // TODO: is this inefficient? + auto src_val = src_vr.read_value(0, byte_count); + LOG_DEBUG("src_val = " << src_val); + dst_alloc.alloc().write_value(dst_vr.m_offset, ::std::move(src_val)); } } else @@ -2235,6 +3098,7 @@ bool InterpreterThread::call_intrinsic(Value& rv, const ::std::string& name, con // TODO: Use a ValueRef instead? bool InterpreterThread::drop_value(Value ptr, const ::HIR::TypeRef& ty, bool is_shallow/*=false*/) { + TRACE_FUNCTION_R(ptr << ": " << ty << (is_shallow ? " (shallow)" : ""), ""); // TODO: After the drop is done, flag the backing allocation for `ptr` as freed if( is_shallow ) { @@ -2242,7 +3106,7 @@ bool InterpreterThread::drop_value(Value ptr, const ::HIR::TypeRef& ty, bool is_ auto box_ptr_vr = ptr.read_pointer_valref_mut(0, POINTER_SIZE); auto ofs = box_ptr_vr.read_usize(0); auto alloc = box_ptr_vr.get_relocation(0); - if( ofs != 0 || !alloc || !alloc.is_alloc() ) { + if( ofs != Allocation::PTR_BASE || !alloc || !alloc.is_alloc() ) { LOG_ERROR("Attempting to shallow drop with invalid pointer (no relocation or non-zero offset) - " << box_ptr_vr); } @@ -2273,6 +3137,7 @@ bool InterpreterThread::drop_value(Value ptr, const ::HIR::TypeRef& ty, bool is_ case TypeWrapper::Ty::Slice: { // - Get thin pointer and count auto ofs = ptr.read_usize(0); + LOG_ASSERT(ofs >= Allocation::PTR_BASE, ""); auto ptr_reloc = ptr.get_relocation(0); auto count = ptr.read_usize(POINTER_SIZE); @@ -2281,8 +3146,28 @@ bool InterpreterThread::drop_value(Value ptr, const ::HIR::TypeRef& ty, bool is_ for(uint64_t i = 0; i < count; i ++) { auto ptr = Value::new_pointer(pty, ofs, ptr_reloc); - if( !drop_value(ptr, ity) ) { - LOG_TODO("Handle closure looping when dropping a slice"); + if( !drop_value(ptr, ity) ) + { + // - This is trying to invoke custom drop glue, need to suspend this operation and come back later + + // > insert a new frame shim BEFORE the current top (which would be the frame created by + // `drop_value` calling a function) + m_stack.insert( m_stack.end() - 1, StackFrame::make_wrapper([this,pty,ity,ptr_reloc,count, i,ofs](Value& rv, Value drop_rv) mutable { + assert(i < count); + i ++; + ofs += ity.get_size(); + if( i < count ) + { + auto ptr = Value::new_pointer(pty, ofs, ptr_reloc); + assert(!drop_value(ptr, ity)); + return false; + } + else + { + return true; + } + }) ); + return false; } ofs += ity.get_size(); } @@ -2297,12 +3182,12 @@ bool InterpreterThread::drop_value(Value ptr, const ::HIR::TypeRef& ty, bool is_ { if( ty.inner_type == RawType::Composite ) { - if( ty.composite_type->drop_glue != ::HIR::Path() ) + if( ty.composite_type().drop_glue != ::HIR::Path() ) { LOG_DEBUG("Drop - " << ty); Value tmp; - return this->call_path(tmp, ty.composite_type->drop_glue, { ptr }); + return this->call_path(tmp, ty.composite_type().drop_glue, { ptr }); } else { @@ -2311,7 +3196,21 @@ bool InterpreterThread::drop_value(Value ptr, const ::HIR::TypeRef& ty, bool is_ } else if( ty.inner_type == RawType::TraitObject ) { - LOG_TODO("Drop - " << ty << " - trait object"); + // Get the drop glue from the vtable (first entry) + auto inner_ptr = ptr.read_value(0, POINTER_SIZE); + auto vtable = ptr.deref(POINTER_SIZE, ty.get_meta_type().get_inner()); + auto drop_r = vtable.get_relocation(0); + if( drop_r ) + { + LOG_ASSERT(drop_r.get_ty() == RelocationPtr::Ty::Function, ""); + auto fcn = drop_r.fcn(); + static Value tmp; + return this->call_path(tmp, fcn, { ::std::move(inner_ptr) }); + } + else + { + // None + } } else { |