summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Hodge <tpg@mutabah.net>2018-02-16 20:51:04 +0800
committerJohn Hodge <tpg@mutabah.net>2018-02-16 20:51:04 +0800
commit75413b30fcf5dd97ddf44daa90fe1e361098d9e0 (patch)
tree18dbe1eb09bf0491194255813134b33814d82ac3
parent97156f41a6831d0bfb8870a3757a2b19cd4d2495 (diff)
downloadmrust-75413b30fcf5dd97ddf44daa90fe1e361098d9e0.tar.gz
Standalone MIRI - Hacking along, hit a blocker that will need some refactor.
-rw-r--r--src/trans/codegen_mmir.cpp23
-rw-r--r--tools/standalone_miri/debug.cpp42
-rw-r--r--tools/standalone_miri/debug.hpp38
-rw-r--r--tools/standalone_miri/hir_sim.cpp16
-rw-r--r--tools/standalone_miri/hir_sim.hpp10
-rw-r--r--tools/standalone_miri/main.cpp229
-rw-r--r--tools/standalone_miri/module_tree.cpp126
-rw-r--r--tools/standalone_miri/module_tree.hpp9
-rw-r--r--tools/standalone_miri/value.cpp165
-rw-r--r--tools/standalone_miri/value.hpp27
-rw-r--r--vsproject/standalone_miri/standalone_miri.vcxproj7
-rw-r--r--vsproject/standalone_miri/standalone_miri.vcxproj.filters6
12 files changed, 605 insertions, 93 deletions
diff --git a/src/trans/codegen_mmir.cpp b/src/trans/codegen_mmir.cpp
index 3f947b46..345a5703 100644
--- a/src/trans/codegen_mmir.cpp
+++ b/src/trans/codegen_mmir.cpp
@@ -424,15 +424,20 @@ namespace
TU_ARM(repr->variants, None, _e) (void)_e;
break;
TU_ARM(repr->variants, Values, e)
- for(auto v : e.values)
+ for(const auto& v : e.values)
{
- m_of << "\t[" << e.field.index << ", " << e.field.sub_fields << "] = \"";
+ // Variants require:
+ m_of << "\t#" << (&v - e.values.data());
+ // - Data field number (optional)
+ if( !item.is_value() )
+ {
+ m_of << " =" << (&v - e.values.data());
+ }
+ // - Tag offsetr
+ m_of << " @[" << e.field.index << ", " << e.field.sub_fields << "] = \"";
for(size_t i = 0; i < e.field.size; i ++)
{
int val = (v >> (i*8)) & 0xFF;
- //if( val == 0 )
- // m_of << "\\0";
- //else
if(val < 16)
m_of << ::std::hex << "\\x0" << val << ::std::dec;
else
@@ -442,12 +447,13 @@ namespace
}
break;
TU_ARM(repr->variants, NonZero, e) {
- m_of << "\t[" << e.field.index << ", " << e.field.sub_fields << "] = \"";
+ m_of << "\t#" << int(e.zero_variant) << " @[" << e.field.index << ", " << e.field.sub_fields << "] = \"";
for(size_t i = 0; i < e.field.size; i ++)
{
m_of << "\\0";
}
m_of << "\";\n";
+ m_of << "\t#" << int(1 - e.zero_variant) << ";\n";
} break;
}
m_of << "}\n";
@@ -805,6 +811,11 @@ namespace
m_mir_res = &mir_res;
m_of << "fn " << p << "(";
+ for(unsigned int i = 0; i < item.m_args.size(); i ++)
+ {
+ if( i != 0 ) m_of << ", ";
+ m_of << params.monomorph(m_resolve, item.m_args[i].second);
+ }
m_of << "): " << ret_type << " {\n";
for(unsigned int i = 0; i < code->locals.size(); i ++) {
DEBUG("var" << i << " : " << code->locals[i]);
diff --git a/tools/standalone_miri/debug.cpp b/tools/standalone_miri/debug.cpp
new file mode 100644
index 00000000..503d775a
--- /dev/null
+++ b/tools/standalone_miri/debug.cpp
@@ -0,0 +1,42 @@
+#include "debug.hpp"
+
+DebugSink::DebugSink(::std::ostream& inner):
+ m_inner(inner)
+{
+}
+DebugSink::~DebugSink()
+{
+ m_inner << "\n";
+}
+bool DebugSink::enabled(const char* fcn_name)
+{
+ return true;
+}
+DebugSink DebugSink::get(const char* fcn_name, const char* file, unsigned line, DebugLevel lvl)
+{
+ switch(lvl)
+ {
+ case DebugLevel::Trace:
+ ::std::cout << "Trace: " << file << ":" << line << ": ";
+ break;
+ case DebugLevel::Debug:
+ ::std::cout << "DEBUG: " << fcn_name << ": ";
+ break;
+ case DebugLevel::Notice:
+ ::std::cout << "NOTE: ";
+ break;
+ case DebugLevel::Warn:
+ ::std::cout << "WARN: ";
+ break;
+ case DebugLevel::Error:
+ ::std::cout << "ERROR: ";
+ break;
+ case DebugLevel::Fatal:
+ ::std::cout << "FATAL: ";
+ break;
+ case DebugLevel::Bug:
+ ::std::cout << "BUG: " << fcn_name << ": ";
+ break;
+ }
+ return DebugSink(::std::cout);
+} \ No newline at end of file
diff --git a/tools/standalone_miri/debug.hpp b/tools/standalone_miri/debug.hpp
new file mode 100644
index 00000000..77295aa2
--- /dev/null
+++ b/tools/standalone_miri/debug.hpp
@@ -0,0 +1,38 @@
+//
+//
+//
+#pragma once
+
+#include <iostream>
+
+enum class DebugLevel {
+ Trace,
+ Debug,
+ Notice,
+ Warn,
+ Error,
+ Fatal,
+ Bug,
+};
+
+class DebugSink
+{
+ ::std::ostream& m_inner;
+ DebugSink(::std::ostream& inner);
+public:
+ ~DebugSink();
+
+ template<typename T>
+ ::std::ostream& operator<<(const T& v) { return m_inner << v; }
+
+ static bool enabled(const char* fcn_name);
+ static DebugSink get(const char* fcn_name, const char* file, unsigned line, DebugLevel lvl);
+};
+
+#define LOG_TRACE(strm) do { if(DebugSink::enabled(__FUNCTION__)) DebugSink::get(__FUNCTION__,__FILE__,__LINE__,DebugLevel::Trace) << strm; } while(0)
+#define LOG_DEBUG(strm) do { if(DebugSink::enabled(__FUNCTION__)) DebugSink::get(__FUNCTION__,__FILE__,__LINE__,DebugLevel::Debug) << strm; } while(0)
+#define LOG_ERROR(strm) do { DebugSink::get(__FUNCTION__,__FILE__,__LINE__,DebugLevel::Error) << strm; exit(1); } while(0)
+#define LOG_FATAL(strm) do { DebugSink::get(__FUNCTION__,__FILE__,__LINE__,DebugLevel::Fatal) << strm; exit(1); } while(0)
+#define LOG_TODO(strm) do { DebugSink::get(__FUNCTION__,__FILE__,__LINE__,DebugLevel::Bug) << "TODO: " << strm; abort(); } while(0)
+#define LOG_BUG(strm) do { DebugSink::get(__FUNCTION__,__FILE__,__LINE__,DebugLevel::Bug) << "BUG: " << strm; abort(); } while(0)
+#define LOG_ASSERT(cnd) do { if( !(cnd) ) { LOG_BUG("Assertion failure: " #cnd); } } while(0)
diff --git a/tools/standalone_miri/hir_sim.cpp b/tools/standalone_miri/hir_sim.cpp
index c4232343..d3ccddc7 100644
--- a/tools/standalone_miri/hir_sim.cpp
+++ b/tools/standalone_miri/hir_sim.cpp
@@ -133,6 +133,22 @@ HIR::TypeRef HIR::TypeRef::get_field(size_t idx, size_t& ofs) const
throw "ERROR";
}
}
+size_t HIR::TypeRef::get_field_ofs(size_t base_idx, const ::std::vector<size_t>& other_idx, TypeRef& ty) const
+{
+ assert(this->wrappers.size() == 0);
+ assert(this->inner_type == RawType::Composite);
+ size_t ofs = this->composite_type->fields.at(base_idx).first;
+ const auto* ty_p = &this->composite_type->fields.at(base_idx).second;
+ for(auto idx : other_idx)
+ {
+ assert(ty_p->wrappers.size() == 0);
+ assert(ty_p->inner_type == RawType::Composite);
+ ofs += ty_p->composite_type->fields.at(idx).first;
+ ty_p = &ty_p->composite_type->fields.at(idx).second;
+ }
+ ty = *ty_p;
+ return ofs;
+}
namespace HIR {
::std::ostream& operator<<(::std::ostream& os, const ::HIR::BorrowType& x)
diff --git a/tools/standalone_miri/hir_sim.hpp b/tools/standalone_miri/hir_sim.hpp
index 96887536..d0f04578 100644
--- a/tools/standalone_miri/hir_sim.hpp
+++ b/tools/standalone_miri/hir_sim.hpp
@@ -119,6 +119,14 @@ namespace HIR {
TypeRef get_inner() const;
TypeRef get_field(size_t idx, size_t& ofs) const;
+ bool operator==(const RawType& x) const {
+ if( this->wrappers.size() != 0 )
+ return false;
+ return this->inner_type == x;
+ }
+ bool operator!=(const RawType& x) const {
+ return !(*this == x);
+ }
bool operator==(const TypeRef& x) const {
return !(*this != x);
}
@@ -135,6 +143,8 @@ namespace HIR {
return false;
}
+ size_t get_field_ofs(size_t idx, const ::std::vector<size_t>& other_idx, TypeRef& ty) const;
+
friend ::std::ostream& operator<<(::std::ostream& os, const TypeRef& x);
};
diff --git a/tools/standalone_miri/main.cpp b/tools/standalone_miri/main.cpp
index 8a54c321..d1163c14 100644
--- a/tools/standalone_miri/main.cpp
+++ b/tools/standalone_miri/main.cpp
@@ -6,6 +6,7 @@
#include "value.hpp"
#include <algorithm>
#include <iomanip>
+#include "debug.hpp"
struct ProgramOptions
{
@@ -48,12 +49,11 @@ int main(int argc, const char* argv[])
Value MIRI_Invoke(ModuleTree& modtree, ::HIR::Path path, ::std::vector<Value> args)
{
- //TRACE_FUNCTION_FR(path,path)
+ LOG_DEBUG(path);
const auto& fcn = modtree.get_function(path);
for(size_t i = 0; i < args.size(); i ++)
{
- //DEBUG(a);
- ::std::cout << "Argument(" << i << ") = " << args[i] << ::std::endl;
+ LOG_DEBUG("- Argument(" << i << ") = " << args[i]);
}
::std::vector<bool> drop_flags = fcn.m_mir.drop_flags;
@@ -134,7 +134,7 @@ Value MIRI_Invoke(ModuleTree& modtree, ::HIR::Path path, ::std::vector<Value> ar
TU_ARM(lv, Field, e) {
::HIR::TypeRef composite_ty;
auto& base_val = get_value_type_and_ofs(*e.val, ofs, composite_ty);
- ::std::cout << "get_type_and_ofs: " << composite_ty << ::std::endl;
+ LOG_DEBUG("Field - " << composite_ty);
size_t inner_ofs;
ty = composite_ty.get_field(e.field_index, inner_ofs);
ofs += inner_ofs;
@@ -146,16 +146,23 @@ Value MIRI_Invoke(ModuleTree& modtree, ::HIR::Path path, ::std::vector<Value> ar
size_t inner_ofs;
ty = composite_ty.get_field(e.variant_index, inner_ofs);
- ::std::cerr << "TODO: Read from Downcast - " << lv << ::std::endl;
- throw "TODO";
+ LOG_TODO("Read from Downcast - " << lv);
ofs += inner_ofs;
return base_val;
}
TU_ARM(lv, Deref, e) {
- //auto addr = read_lvalue(*e.val);
+ ::HIR::TypeRef ptr_ty;
+ auto addr = read_lvalue_with_ty(*e.val, ptr_ty);
+ // There MUST be a relocation at this point with a valid allocation.
+ auto& reloc = addr.allocation.alloc().relocations.at(0);
+ assert(reloc.slot_ofs == 0);
+ ofs = addr.read_usize(0);
+ // Need to make a new Value to return as the base value.
+ // - This value needs to be stored somewhere.
+ // - Shoudl the value directly reference into the pointer?
+ //auto rv = Value(ptr_ty.get_inner(), reloc.backing_alloc, ofs);
+ LOG_TODO("Read from deref - " << lv << " - " << addr);
- ::std::cerr << "TODO: Read from deref - " << lv << ::std::endl;
- throw "TODO";
} break;
}
throw "";
@@ -171,24 +178,19 @@ Value MIRI_Invoke(ModuleTree& modtree, ::HIR::Path path, ::std::vector<Value> ar
Value read_lvalue_with_ty(const ::MIR::LValue& lv, ::HIR::TypeRef& ty)
{
- ::std::cout << "read_lvalue_with_ty: " << lv << ::std::endl;
size_t ofs = 0;
Value& base_value = get_value_type_and_ofs(lv, ofs, ty);
- ::std::cout << "> read_lvalue_with_ty: " << ty << ::std::endl;
-
return base_value.read_value(ofs, ty.get_size());
}
Value read_lvalue(const ::MIR::LValue& lv)
{
- ::std::cout << "read_lvalue: " << lv << ::std::endl;
::HIR::TypeRef ty;
return read_lvalue_with_ty(lv, ty);
}
void write_lvalue(const ::MIR::LValue& lv, Value val)
{
- ::std::cout << "write_lvaue: " << lv << ::std::endl;
- //::std::cout << "write_lvaue: " << lv << " = " << val << ::std::endl;
+ //LOG_DEBUG(lv << " = " << val);
::HIR::TypeRef ty;
size_t ofs = 0;
Value& base_value = get_value_type_and_ofs(lv, ofs, ty);
@@ -284,7 +286,7 @@ Value MIRI_Invoke(ModuleTree& modtree, ::HIR::Path path, ::std::vector<Value> ar
for(const auto& stmt : bb.statements)
{
- ::std::cout << "BB" << bb_idx << "/" << (&stmt - bb.statements.data()) << ": " << stmt << ::std::endl;
+ LOG_DEBUG("BB" << bb_idx << "/" << (&stmt - bb.statements.data()) << ": " << stmt);
switch(stmt.tag())
{
case ::MIR::Statement::TAGDEAD: throw "";
@@ -316,18 +318,180 @@ Value MIRI_Invoke(ModuleTree& modtree, ::HIR::Path path, ::std::vector<Value> ar
// ^ Pointer value
new_val.allocation.alloc().relocations.push_back(Relocation { 0, src_base_value.allocation });
new_val.write_bytes(0, &ofs, src_ty.get_size());
+ LOG_DEBUG("- " << new_val);
::HIR::TypeRef dst_ty;
// TODO: Check type equality
size_t dst_ofs = 0;
Value& dst_base_value = state.get_value_type_and_ofs(se.dst, dst_ofs, dst_ty);
dst_base_value.write_value(dst_ofs, ::std::move(new_val));
+ LOG_DEBUG("- " << dst_base_value);
} break;
TU_ARM(se.src, SizedArray, re) {
throw "TODO";
} break;
TU_ARM(se.src, Cast, re) {
- throw "TODO";
+ // Determine the type of cast, is it a reinterpret or is it a value transform?
+ // - Float <-> integer is a transform, anything else should be a reinterpret.
+ ::HIR::TypeRef src_ty;
+ Value src_value = state.read_lvalue_with_ty(re.val, src_ty);
+
+ ::HIR::TypeRef dst_ty;
+ size_t dst_ofs = 0;
+ Value& dst_base_value = state.get_value_type_and_ofs(se.dst, dst_ofs, dst_ty);
+
+ Value new_val = Value(re.type);
+ if( re.type == src_ty )
+ {
+ // No-op cast
+ new_val = ::std::move(src_value);
+ }
+ else if( !re.type.wrappers.empty() )
+ {
+ // Destination can only be a raw pointer
+ if( re.type.wrappers.at(0).type != TypeWrapper::Ty::Pointer ) {
+ throw "ERROR";
+ }
+ if( !src_ty.wrappers.empty() )
+ {
+ // Source can be either
+ if( src_ty.wrappers.at(0).type != TypeWrapper::Ty::Pointer
+ && src_ty.wrappers.at(0).type != TypeWrapper::Ty::Borrow ) {
+ throw "ERROR";
+ }
+
+ if( src_ty.get_size() > re.type.get_size() ) {
+ // TODO: How to casting fat to thin?
+ throw "TODO";
+ }
+ else
+ {
+ new_val = ::std::move(src_value);
+ }
+ }
+ else
+ {
+ if( src_ty == RawType::Function )
+ {
+ }
+ else if( src_ty == RawType::USize )
+ {
+ }
+ else
+ {
+ ::std::cerr << "ERROR: Trying to pointer (" << re.type <<" ) from invalid type (" << src_ty << ")\n";
+ throw "ERROR";
+ }
+ new_val = ::std::move(src_value);
+ }
+ }
+ else if( !src_ty.wrappers.empty() )
+ {
+ // TODO: top wrapper MUST be a pointer
+ if( src_ty.wrappers.at(0).type != TypeWrapper::Ty::Pointer
+ && src_ty.wrappers.at(0).type != TypeWrapper::Ty::Borrow ) {
+ throw "ERROR";
+ }
+ // TODO: MUST be a thin pointer
+
+ // TODO: MUST be an integer (usize only?)
+ if( src_ty != RawType::USize ) {
+ throw "ERROR";
+ }
+ new_val = ::std::move(src_value);
+ }
+ else
+ {
+ // TODO: What happens if there'a cast of something with a relocation?
+ switch(re.type.inner_type)
+ {
+ case RawType::Unreachable: throw "BUG";
+ case RawType::Composite: throw "ERROR";
+ case RawType::TraitObject: throw "ERROR";
+ case RawType::Function: throw "ERROR";
+ case RawType::Str: throw "ERROR";
+ case RawType::Unit: throw "ERROR";
+ case RawType::F32: {
+ float dst_val = 0.0;
+ // Can be an integer, or F64 (pointer is impossible atm)
+ switch(src_ty.inner_type)
+ {
+ case RawType::Unreachable: throw "BUG";
+ case RawType::Composite: throw "ERROR";
+ case RawType::TraitObject: throw "ERROR";
+ case RawType::Function: throw "ERROR";
+ case RawType::Char: throw "ERROR";
+ case RawType::Str: throw "ERROR";
+ case RawType::Unit: throw "ERROR";
+ 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::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;
+ case RawType::I16: dst_val = static_cast<float>( src_value.read_i16(0) ); break;
+ case RawType::U32: dst_val = static_cast<float>( src_value.read_u32(0) ); break;
+ 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;
+ }
+ new_val.write_f32(0, dst_val);
+ } break;
+ case RawType::F64: {
+ double dst_val = 0.0;
+ // Can be an integer, or F32 (pointer is impossible atm)
+ switch(src_ty.inner_type)
+ {
+ case RawType::Unreachable: throw "BUG";
+ case RawType::Composite: throw "ERROR";
+ case RawType::TraitObject: throw "ERROR";
+ case RawType::Function: throw "ERROR";
+ case RawType::Char: throw "ERROR";
+ case RawType::Str: throw "ERROR";
+ case RawType::Unit: throw "ERROR";
+ case RawType::Bool: throw "ERROR";
+ case RawType::F64: throw "BUG";
+ case RawType::F32: dst_val = static_cast<double>( src_value.read_f32(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::U8: dst_val = static_cast<double>( src_value.read_u8 (0) ); break;
+ case RawType::I8: dst_val = static_cast<double>( src_value.read_i8 (0) ); break;
+ case RawType::U16: dst_val = static_cast<double>( src_value.read_u16(0) ); break;
+ case RawType::I16: dst_val = static_cast<double>( src_value.read_i16(0) ); break;
+ case RawType::U32: dst_val = static_cast<double>( src_value.read_u32(0) ); break;
+ case RawType::I32: dst_val = static_cast<double>( src_value.read_i32(0) ); break;
+ case RawType::U64: dst_val = static_cast<double>( src_value.read_u64(0) ); break;
+ case RawType::I64: dst_val = static_cast<double>( 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;
+ }
+ new_val.write_f64(0, dst_val);
+ } break;
+ case RawType::Bool:
+ throw "TODO";
+ case RawType::Char:
+ throw "TODO";
+ case RawType::USize:
+ case RawType::ISize:
+ case RawType::U8:
+ case RawType::I8:
+ case RawType::U16:
+ case RawType::I16:
+ case RawType::U32:
+ case RawType::I32:
+ case RawType::U64:
+ case RawType::I64:
+ case RawType::U128:
+ case RawType::I128:
+ throw "TODO";
+ }
+ }
+
+ dst_base_value.write_value(dst_ofs, ::std::move(new_val));
} break;
TU_ARM(se.src, BinOp, re) {
throw "TODO";
@@ -359,7 +523,34 @@ Value MIRI_Invoke(ModuleTree& modtree, ::HIR::Path path, ::std::vector<Value> ar
throw "TODO";
} break;
TU_ARM(se.src, Variant, re) {
- throw "TODO";
+ ::HIR::TypeRef dst_ty;
+ size_t dst_ofs = 0;
+ Value& dst_base_value = state.get_value_type_and_ofs(se.dst, dst_ofs, dst_ty);
+
+ // 1. Get the composite by path.
+ const auto& ty = state.modtree.get_composite(re.path);
+ // Three cases:
+ // - Unions (no tag)
+ // - Data enums (tag and data)
+ // - Value enums (no data)
+ const auto& var = ty.variants.at(re.index);
+ if( var.data_field != SIZE_MAX )
+ {
+ const auto& fld = ty.fields.at(re.index);
+
+ dst_base_value.write_value(dst_ofs + fld.first, state.param_to_value(re.val));
+ }
+ if( var.base_field != SIZE_MAX )
+ {
+ ::HIR::TypeRef tag_ty;
+ size_t tag_ofs = dst_ty.get_field_ofs(var.base_field, var.field_path, tag_ty);
+ LOG_ASSERT(tag_ty.get_size() == var.tag_data.size());
+ dst_base_value.write_bytes(dst_ofs + tag_ofs, var.tag_data.data(), var.tag_data.size());
+ }
+ else
+ {
+ // Union, no tag
+ }
} break;
TU_ARM(se.src, Struct, re) {
throw "TODO";
@@ -416,7 +607,7 @@ Value MIRI_Invoke(ModuleTree& modtree, ::HIR::Path path, ::std::vector<Value> ar
auto v = state.read_lvalue_with_ty(te.fcn.as_Value(), ty);
// TODO: Assert type
// TODO: Assert offset/content.
- assert(v.as_usize() == 0);
+ assert(v.read_usize(0) == 0);
fcn_p = &v.allocation.alloc().relocations.at(0).backing_alloc.fcn();
}
diff --git a/tools/standalone_miri/module_tree.cpp b/tools/standalone_miri/module_tree.cpp
index 90d599c8..321eaad9 100644
--- a/tools/standalone_miri/module_tree.cpp
+++ b/tools/standalone_miri/module_tree.cpp
@@ -5,6 +5,7 @@
#include "lex.hpp"
#include "value.hpp"
#include <iostream>
+#include "debug.hpp"
ModuleTree::ModuleTree()
{
@@ -109,8 +110,31 @@ bool Parser::parse_one()
{
while( !lex.consume_if('}') )
{
- // TODO: Parse relocation entries
- throw "TODO";
+ lex.check_consume('@');
+ lex.check(TokenClass::Integer);
+ auto ofs = lex.consume().integer();
+ lex.check_consume('+');
+ lex.check(TokenClass::Integer);
+ auto size = lex.consume().integer();
+ lex.check_consume('=');
+ if( lex.next() == TokenClass::String )
+ {
+ auto reloc_str = ::std::move(lex.consume().strval);
+ // TODO: Add relocation
+ }
+ else if( lex.next() == "::" )
+ {
+ auto reloc_path = parse_path();
+ // TODO: Add relocation
+ }
+ else
+ {
+ throw "ERROR";
+ }
+ if( ! lex.consume_if(',') ) {
+ lex.check_consume('}');
+ break ;
+ }
}
}
lex.check_consume(';');
@@ -147,50 +171,76 @@ bool Parser::parse_one()
//rv->dst_meta = ::HIR::TypeRef::diverge();
}
- // Data
- while(lex.next() == TokenClass::Integer)
+ while( lex.next() != '}' )
{
- size_t ofs = lex.consume().integer();
- lex.check_consume('=');
- auto ty = parse_type();
- lex.check_consume(';');
- //::std::cout << ofs << " " << ty << ::std::endl;
+ // Data
+ if(lex.next() == TokenClass::Integer)
+ {
+ size_t ofs = lex.consume().integer();
+ lex.check_consume('=');
+ auto ty = parse_type();
+ lex.check_consume(';');
+ //::std::cout << ofs << " " << ty << ::std::endl;
- rv.fields.push_back(::std::make_pair(ofs, ::std::move(ty)));
- }
- // Variants
- while(lex.next() == '[')
- {
- lex.consume();
- size_t base_idx = lex.consume().integer();
- ::std::vector<size_t> other_idx;
- if( lex.consume_if(',') )
+ rv.fields.push_back(::std::make_pair(ofs, ::std::move(ty)));
+ }
+ // Variants
+ else if(lex.next() == TokenClass::Ident && lex.next().strval[0] == '#' )
{
- while(lex.next() != ']')
+ size_t var_idx = ::std::stoi(lex.consume().strval.substr(1));
+
+ size_t data_fld = SIZE_MAX;
+ if( lex.consume_if('=') )
{
- lex.check(TokenClass::Integer);
- other_idx.push_back( lex.consume().integer() );
- if( !lex.consume_if(',') )
- break;
+ data_fld = lex.consume().integer();
}
- }
- lex.check_consume(']');
- lex.check_consume('=');
- lex.check(TokenClass::String);
- uint64_t v = 0;
- int pos = 0;
- for(auto ch : lex.next().strval)
- {
- if(pos < 64)
+
+ size_t tag_ofs = SIZE_MAX;
+
+ size_t base_idx = SIZE_MAX;
+ ::std::vector<size_t> other_idx;
+ ::std::string tag_value;
+ if( lex.consume_if('@') )
+ {
+ lex.check_consume('[');
+ base_idx = lex.consume().integer();
+ if( lex.consume_if(',') )
+ {
+ while(lex.next() != ']')
+ {
+ lex.check(TokenClass::Integer);
+ other_idx.push_back( lex.consume().integer() );
+ if( !lex.consume_if(',') )
+ break;
+ }
+ }
+ lex.check_consume(']');
+
+ lex.check_consume('=');
+ lex.check(TokenClass::String);
+ tag_value = ::std::move(lex.consume().strval);
+
+ //tag_ofs = rv.fields.at(base_idx).first;
+ //const auto* tag_ty = &rv.fields.at(base_idx).second;
+ //for(auto idx : other_idx)
+ //{
+ // assert(tag_ty->wrappers.size() == 0);
+ // assert(tag_ty->inner_type == RawType::Composite);
+ // LOG_TODO(lex << "Calculate tag offset with nested tag - " << idx << " ty=" << *tag_ty);
+ //}
+ }
+ lex.check_consume(';');
+
+ if( rv.variants.size() <= var_idx )
{
- v |= static_cast<uint64_t>(ch) << pos;
+ rv.variants.resize(var_idx+1);
}
- pos += 8;
+ rv.variants[var_idx] = { data_fld, base_idx, other_idx, tag_value };
+ }
+ else
+ {
+ LOG_BUG("");
}
- lex.consume();
- lex.check_consume(';');
-
- rv.variants.push_back({ base_idx, other_idx, v });
}
lex.check_consume('}');
diff --git a/tools/standalone_miri/module_tree.hpp b/tools/standalone_miri/module_tree.hpp
index 96a77718..dd302367 100644
--- a/tools/standalone_miri/module_tree.hpp
+++ b/tools/standalone_miri/module_tree.hpp
@@ -43,6 +43,10 @@ public:
const Function* get_function_opt(const ::HIR::Path& p) const;
Value& get_static(const ::HIR::Path& p);
Value* get_static_opt(const ::HIR::Path& p);
+
+ const DataType& get_composite(const ::HIR::GenericPath& p) const {
+ return *data_types.at(p);
+ }
};
// struct/union/enum
@@ -57,9 +61,12 @@ struct DataType
::std::vector<::std::pair<size_t, ::HIR::TypeRef>> fields;
// Values for variants
struct VariantValue {
+ size_t data_field;
size_t base_field;
::std::vector<size_t> field_path;
- uint64_t value; // TODO: This should be arbitary data? what size?
+
+ //size_t tag_offset; // Cached.
+ ::std::string tag_data;
};
::std::vector<VariantValue> variants;
};
diff --git a/tools/standalone_miri/value.cpp b/tools/standalone_miri/value.cpp
index db8ec085..e4a19196 100644
--- a/tools/standalone_miri/value.cpp
+++ b/tools/standalone_miri/value.cpp
@@ -7,7 +7,7 @@
#include <iostream>
#include <iomanip>
#include <algorithm>
-
+#include "debug.hpp"
AllocationPtr Allocation::new_alloc(size_t size)
{
@@ -15,6 +15,7 @@ AllocationPtr Allocation::new_alloc(size_t size)
rv->refcount = 1;
rv->data.resize( (size + 8-1) / 8 ); // QWORDS
rv->mask.resize( (size + 8-1) / 8 ); // bitmap bytes
+ //LOG_DEBUG(rv << " ALLOC");
return AllocationPtr(rv);
}
AllocationPtr AllocationPtr::new_fcn(::HIR::Path p)
@@ -25,11 +26,34 @@ AllocationPtr AllocationPtr::new_fcn(::HIR::Path p)
return rv;
}
AllocationPtr::AllocationPtr(const AllocationPtr& x):
- m_ptr(x.m_ptr)
+ m_ptr(nullptr)
{
- if( is_alloc() ) {
- assert(alloc().refcount != SIZE_MAX);
- alloc().refcount += 1;
+ if( x )
+ {
+ switch(x.get_ty())
+ {
+ case Ty::Allocation:
+ m_ptr = x.m_ptr;
+ assert(alloc().refcount != 0);
+ assert(alloc().refcount != SIZE_MAX);
+ alloc().refcount += 1;
+ //LOG_DEBUG(&alloc() << " REF++ " << alloc().refcount);
+ break;
+ case Ty::Function: {
+ auto ptr_i = reinterpret_cast<uintptr_t>(new ::HIR::Path(x.fcn()));
+ assert( (ptr_i & 3) == 0 );
+ m_ptr = reinterpret_cast<void*>( ptr_i + static_cast<uintptr_t>(Ty::Function) );
+ assert(get_ty() == Ty::Function);
+ } break;
+ case Ty::Unused1:
+ throw "BUG";
+ case Ty::Unused2:
+ throw "BUG";
+ }
+ }
+ else
+ {
+ m_ptr = nullptr;
}
}
AllocationPtr::~AllocationPtr()
@@ -41,8 +65,11 @@ AllocationPtr::~AllocationPtr()
case Ty::Allocation: {
auto* ptr = &alloc();
ptr->refcount -= 1;
+ //LOG_DEBUG(&alloc() << " REF-- " << ptr->refcount);
if(ptr->refcount == 0)
+ {
delete ptr;
+ }
} break;
case Ty::Function: {
auto* ptr = const_cast<::HIR::Path*>(&fcn());
@@ -56,6 +83,31 @@ AllocationPtr::~AllocationPtr()
}
}
+::std::ostream& operator<<(::std::ostream& os, const AllocationPtr& x)
+{
+ if( x )
+ {
+ switch(x.get_ty())
+ {
+ case AllocationPtr::Ty::Allocation:
+ os << &x.alloc();
+ break;
+ case AllocationPtr::Ty::Function:
+ os << *const_cast<::HIR::Path*>(&x.fcn());
+ break;
+ case AllocationPtr::Ty::Unused1:
+ break;
+ case AllocationPtr::Ty::Unused2:
+ break;
+ }
+ }
+ else
+ {
+ os << "null";
+ }
+ return os;
+}
+
Value::Value()
{
this->meta.direct_data.size = 0;
@@ -99,7 +151,7 @@ Value::Value(::HIR::TypeRef ty)
if( ! H::has_pointer(ty) )
{
// Will fit in a inline allocation, nice.
- ::std::cout << "Value::Value(): No pointers in " << ty << ", storing inline" << ::std::endl;
+ //LOG_TRACE("No pointers in " << ty << ", storing inline");
this->meta.direct_data.size = static_cast<uint8_t>(size);
this->meta.direct_data.mask[0] = 0;
this->meta.direct_data.mask[1] = 0;
@@ -109,7 +161,7 @@ Value::Value(::HIR::TypeRef ty)
#endif
// Fallback: Make a new allocation
- ::std::cout << "Value::Value(): Creating allocation for " << ty << ::std::endl;
+ //LOG_TRACE(" Creating allocation for " << ty);
this->allocation = Allocation::new_alloc(size);
this->meta.indirect_meta.offset = 0;
this->meta.indirect_meta.size = size;
@@ -174,7 +226,7 @@ void Value::mark_bytes_valid(size_t ofs, size_t size)
auto& alloc = this->allocation.alloc();
// TODO: Assert range.
ofs += this->meta.indirect_meta.offset;
- assert( (ofs+size+8-1) / 8 < alloc.mask.size() );
+ assert( ofs+size <= alloc.mask.size() * 8 );
for(size_t i = ofs; i < ofs + size; i++)
{
alloc.mask[i/8] |= (1 << i%8);
@@ -191,7 +243,8 @@ void Value::mark_bytes_valid(size_t ofs, size_t size)
Value Value::read_value(size_t ofs, size_t size) const
{
- ::std::cout << "Value::read_value(" << ofs << ", " << size << ")" << ::std::endl;
+ Value rv;
+ LOG_DEBUG("(" << ofs << ", " << size << ") - " << *this);
check_bytes_valid(ofs, size);
if( this->allocation )
{
@@ -205,28 +258,34 @@ Value Value::read_value(size_t ofs, size_t size) const
has_reloc = true;
}
}
- Value rv;
- if( has_reloc && size < sizeof(this->meta.direct_data.data) )
+ if( has_reloc || size > sizeof(this->meta.direct_data.data) )
{
rv.allocation = Allocation::new_alloc(size);
rv.meta.indirect_meta.offset = 0;
rv.meta.indirect_meta.size = size;
+
+ for(const auto& r : alloc.relocations)
+ {
+ if( this->meta.indirect_meta.offset+ofs <= r.slot_ofs && r.slot_ofs < this->meta.indirect_meta.offset + ofs + size )
+ {
+ rv.allocation.alloc().relocations.push_back({ r.slot_ofs - (this->meta.indirect_meta.offset+ofs), r.backing_alloc });
+ }
+ }
}
else
{
rv.meta.direct_data.size = static_cast<uint8_t>(size);
}
rv.write_bytes(0, this->data_ptr() + ofs, size);
- return rv;
}
else
{
// Inline can become inline.
- Value rv;
rv.meta.direct_data.size = static_cast<uint8_t>(size);
rv.write_bytes(0, this->meta.direct_data.data+ofs, size);
- return rv;
}
+ LOG_DEBUG("RETURN " << rv);
+ return rv;
}
void Value::read_bytes(size_t ofs, void* dst, size_t count) const
{
@@ -250,6 +309,23 @@ void Value::write_bytes(size_t ofs, const void* src, size_t count)
::std::cerr << "Value::write_bytes - Out of bounds write, " << ofs << "+" << count << " > size " << this->meta.indirect_meta.size << ::std::endl;
throw "ERROR";
}
+
+
+ // - Remove any relocations already within this region
+ auto& this_relocs = this->allocation.alloc().relocations;
+ for(auto it = this_relocs.begin(); it != this_relocs.end(); )
+ {
+ if( this->meta.indirect_meta.offset + ofs <= it->slot_ofs && it->slot_ofs < this->meta.indirect_meta.offset + ofs + count)
+ {
+ LOG_TRACE("Delete " << it->backing_alloc);
+ it = this_relocs.erase(it);
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
}
else
{
@@ -267,21 +343,49 @@ void Value::write_value(size_t ofs, Value v)
{
if( v.allocation )
{
- v.check_bytes_valid(0, v.meta.indirect_meta.size);
+ size_t v_size = v.meta.indirect_meta.size;
+ v.check_bytes_valid(0, v_size);
const auto& src_alloc = v.allocation.alloc();
- write_bytes(ofs, v.data_ptr(), v.meta.indirect_meta.size);
+ write_bytes(ofs, v.data_ptr(), v_size);
// Find any relocations that apply and copy those in.
- // - Any relocations in the source within `v.meta.indirect_meta.offset` .. `v.meta.indirect_meta.offset + v.meta.indirect_meta.size`
+ // - Any relocations in the source within `v.meta.indirect_meta.offset` .. `v.meta.indirect_meta.offset + v_size`
+ ::std::vector<Relocation> new_relocs;
for(const auto& r : src_alloc.relocations)
{
// TODO: Negative offsets in destination?
- if( v.meta.indirect_meta.offset <= r.slot_ofs && r.slot_ofs < v.meta.indirect_meta.offset + v.meta.indirect_meta.size )
+ if( v.meta.indirect_meta.offset <= r.slot_ofs && r.slot_ofs < v.meta.indirect_meta.offset + v_size )
+ {
+ LOG_TRACE("Copy " << r.backing_alloc);
+ // Applicable, save for later
+ new_relocs.push_back( r );
+ }
+ }
+ if( !new_relocs.empty() )
+ {
+ if( !this->allocation ) {
+ throw ::std::runtime_error("TODO: Writing value with a relocation into a slot without a relocation");
+ }
+ // 1. Remove any relocations already within this region
+ auto& this_relocs = this->allocation.alloc().relocations;
+ for(auto it = this_relocs.begin(); it != this_relocs.end(); )
{
- // Applicable
- if( !this->allocation ) {
- throw ::std::runtime_error("TODO: Writing value with a relocation into a slot without a relocation");
+ if( this->meta.indirect_meta.offset + ofs <= it->slot_ofs && it->slot_ofs < this->meta.indirect_meta.offset + ofs + v_size)
+ {
+ LOG_TRACE("Delete " << it->backing_alloc);
+ it = this_relocs.erase(it);
+ }
+ else
+ {
+ ++it;
}
- this->allocation.alloc().relocations.push_back( r );
+ }
+ // 2. Move the new relocations into this allocation
+ for(auto& r : new_relocs)
+ {
+ LOG_TRACE("Insert " << r.backing_alloc);
+ r.slot_ofs -= v.meta.indirect_meta.offset;
+ r.slot_ofs += this->meta.indirect_meta.offset + ofs;
+ this_relocs.push_back( ::std::move(r) );
}
}
}
@@ -292,13 +396,14 @@ void Value::write_value(size_t ofs, Value v)
}
}
-size_t Value::as_usize() const
+uint64_t Value::read_usize(size_t ofs) const
{
- uint64_t v;
+ uint64_t v = 0;
+ // TODO: Handle different pointer sizes
this->read_bytes(0, &v, 8);
- // TODO: Handle endian and different architectures
return v;
}
+
::std::ostream& operator<<(::std::ostream& os, const Value& v)
{
auto flags = os.flags();
@@ -321,6 +426,16 @@ size_t Value::as_usize() const
os << "--";
}
}
+
+ os << " {";
+ for(const auto& r : alloc.relocations)
+ {
+ if( v.meta.indirect_meta.offset <= r.slot_ofs && r.slot_ofs < v.meta.indirect_meta.offset + v.meta.indirect_meta.size )
+ {
+ os << " @" << (r.slot_ofs - v.meta.indirect_meta.offset) << "=" << r.backing_alloc;
+ }
+ }
+ os << " }";
}
else
{
diff --git a/tools/standalone_miri/value.hpp b/tools/standalone_miri/value.hpp
index 9f9e68f4..b0c649fc 100644
--- a/tools/standalone_miri/value.hpp
+++ b/tools/standalone_miri/value.hpp
@@ -43,6 +43,7 @@ public:
~AllocationPtr();
static AllocationPtr new_fcn(::HIR::Path p);
+ AllocationPtr& operator=(const AllocationPtr& x) = delete;
AllocationPtr& operator=(AllocationPtr&& x) {
this->~AllocationPtr();
this->m_ptr = x.m_ptr;
@@ -70,6 +71,8 @@ public:
Ty get_ty() const {
return static_cast<Ty>( reinterpret_cast<uintptr_t>(m_ptr) & 3 );
}
+
+ friend ::std::ostream& operator<<(::std::ostream& os, const AllocationPtr& x);
private:
void* get_ptr() const {
return reinterpret_cast<void*>( reinterpret_cast<uintptr_t>(m_ptr) & ~3 );
@@ -127,7 +130,29 @@ struct Value
void write_value(size_t ofs, Value v);
void write_bytes(size_t ofs, const void* src, size_t count);
- size_t as_usize() const;
+ uint8_t read_u8(size_t ofs) const { uint8_t rv; read_bytes(ofs, &rv, 1); return rv; }
+ int8_t read_i8(size_t ofs) const { return static_cast<int8_t>(read_u8(ofs)); }
+ uint16_t read_u16(size_t ofs) const { uint16_t rv; read_bytes(ofs, &rv, 2); return rv; }
+ int16_t read_i16(size_t ofs) const { return static_cast<int16_t>(read_u16(ofs)); }
+ uint32_t read_u32(size_t ofs) const { uint32_t rv; read_bytes(ofs, &rv, 4); return rv; }
+ int32_t read_i32(size_t ofs) const { return static_cast<int32_t>(read_u32(ofs)); }
+ uint64_t read_u64(size_t ofs) const { uint64_t rv; read_bytes(ofs, &rv, 8); return rv; }
+ int64_t read_i64(size_t ofs) const { return static_cast<int64_t>(read_u64(ofs)); }
+ float read_f32(size_t ofs) const { float rv; read_bytes(ofs, &rv, 4); return rv; }
+ double read_f64(size_t ofs) const { double rv; read_bytes(ofs, &rv, 8); return rv; }
+ uint64_t read_usize(size_t ofs) const;
+ int64_t read_isize(size_t ofs) const { return static_cast<int64_t>(read_usize(ofs)); }
+
+ void write_u8 (size_t ofs, uint8_t v) { write_bytes(ofs, &v, 1); }
+ void write_u16(size_t ofs, uint16_t v) { write_bytes(ofs, &v, 2); }
+ void write_u32(size_t ofs, uint32_t v) { write_bytes(ofs, &v, 4); }
+ void write_u64(size_t ofs, uint64_t v) { write_bytes(ofs, &v, 8); }
+ void write_i8 (size_t ofs, int8_t v) { write_u8 (ofs, static_cast<uint8_t >(v)); }
+ void write_i16(size_t ofs, int16_t v) { write_u16(ofs, static_cast<uint16_t>(v)); }
+ void write_i32(size_t ofs, int32_t v) { write_u32(ofs, static_cast<uint32_t>(v)); }
+ void write_i64(size_t ofs, int64_t v) { write_u64(ofs, static_cast<uint64_t>(v)); }
+ void write_f32(size_t ofs, float v) { write_bytes(ofs, &v, 4); }
+ void write_f64(size_t ofs, double v) { write_bytes(ofs, &v, 8); }
private:
const uint8_t* data_ptr() const { return allocation ? allocation.alloc().data_ptr() + meta.indirect_meta.offset : meta.direct_data.data; }
uint8_t* data_ptr() { return allocation ? allocation.alloc().data_ptr() + meta.indirect_meta.offset : meta.direct_data.data; }
diff --git a/vsproject/standalone_miri/standalone_miri.vcxproj b/vsproject/standalone_miri/standalone_miri.vcxproj
index ed5dc733..4209e566 100644
--- a/vsproject/standalone_miri/standalone_miri.vcxproj
+++ b/vsproject/standalone_miri/standalone_miri.vcxproj
@@ -121,7 +121,7 @@
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
- <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>WIN32;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(SolutionDir)..\src\include;$(SolutionDir)..\tools\standalone_miri;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<TreatSpecificWarningsAsErrors>4062;4061;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>
@@ -141,7 +141,7 @@
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
- <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(SolutionDir)..\src\include;$(SolutionDir)..\tools\standalone_miri;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<TreatSpecificWarningsAsErrors>4062;4061;%(TreatSpecificWarningsAsErrors)</TreatSpecificWarningsAsErrors>
@@ -157,7 +157,7 @@
<Text Include="ReadMe.txt" />
</ItemGroup>
<ItemGroup>
- <ClInclude Include="..\..\tools\standalone_miri\debug.h" />
+ <ClInclude Include="..\..\tools\standalone_miri\debug.hpp" />
<ClInclude Include="..\..\tools\standalone_miri\hir_sim.hpp" />
<ClInclude Include="..\..\tools\standalone_miri\lex.hpp" />
<ClInclude Include="..\..\tools\standalone_miri\module_tree.hpp" />
@@ -165,6 +165,7 @@
<ClInclude Include="targetver.h" />
</ItemGroup>
<ItemGroup>
+ <ClCompile Include="..\..\tools\standalone_miri\debug.cpp" />
<ClCompile Include="..\..\tools\standalone_miri\hir_sim.cpp" />
<ClCompile Include="..\..\tools\standalone_miri\lex.cpp" />
<ClCompile Include="..\..\tools\standalone_miri\main.cpp" />
diff --git a/vsproject/standalone_miri/standalone_miri.vcxproj.filters b/vsproject/standalone_miri/standalone_miri.vcxproj.filters
index 3010940e..e26bbee5 100644
--- a/vsproject/standalone_miri/standalone_miri.vcxproj.filters
+++ b/vsproject/standalone_miri/standalone_miri.vcxproj.filters
@@ -33,6 +33,9 @@
<ClInclude Include="..\..\tools\standalone_miri\lex.hpp">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\tools\standalone_miri\debug.hpp">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\tools\standalone_miri\main.cpp">
@@ -53,5 +56,8 @@
<ClCompile Include="..\..\tools\standalone_miri\value.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\tools\standalone_miri\debug.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
</Project> \ No newline at end of file