diff options
Diffstat (limited to 'tools')
35 files changed, 3852 insertions, 1087 deletions
diff --git a/tools/common/debug.cpp b/tools/common/debug.cpp index 94d8ed99..3884d651 100644 --- a/tools/common/debug.cpp +++ b/tools/common/debug.cpp @@ -41,6 +41,10 @@ void Debug_EnablePhase(const char* phase_name) { gmDisabledDebug.erase(it); } + else + { + ::std::cerr << "Unknown debug phase: " << phase_name << ::std::endl; + } } void Debug_Print(::std::function<void(::std::ostream& os)> cb) { diff --git a/tools/common/target_detect.h b/tools/common/target_detect.h index 1bfc7dd9..dda4bc31 100644 --- a/tools/common/target_detect.h +++ b/tools/common/target_detect.h @@ -10,9 +10,9 @@ // - Windows (MSVC) #ifdef _MSC_VER # if defined(_WIN64) -# define DEFAULT_TARGET_NAME "x86_64-windows-msvc" +# define DEFAULT_TARGET_NAME "x86_64-pc-windows-msvc" # else -# define DEFAULT_TARGET_NAME "x86-windows-msvc" +# define DEFAULT_TARGET_NAME "x86-pc-windows-msvc" # endif // - Linux #elif defined(__linux__) @@ -32,9 +32,9 @@ // - MinGW #elif defined(__MINGW32__) # if defined(_WIN64) -# define DEFAULT_TARGET_NAME "x86_64-windows-gnu" +# define DEFAULT_TARGET_NAME "x86_64-pc-windows-gnu" # else -# define DEFAULT_TARGET_NAME "i586-windows-gnu" +# define DEFAULT_TARGET_NAME "i586-pc-windows-gnu" # endif // - FreeBSD #elif defined(__FreeBSD__) diff --git a/tools/common/toml.cpp b/tools/common/toml.cpp index 285d22a4..4e8e6da6 100644 --- a/tools/common/toml.cpp +++ b/tools/common/toml.cpp @@ -264,8 +264,7 @@ TomlKeyValue TomlFile::get_next_value() } if( m_current_composite.empty() ) { - // TODO: Allow EOF? - if(t.m_type != Token::Type::Newline) + if(t.m_type != Token::Type::Newline && t.m_type != Token::Type::Eof) throw ::std::runtime_error(::format(m_lexer, ": Unexpected token in TOML file after entry - ", t)); } else diff --git a/tools/common/toml.h b/tools/common/toml.h index 17e05142..5e031803 100644 --- a/tools/common/toml.h +++ b/tools/common/toml.h @@ -58,7 +58,7 @@ public: // Obtain the next value in the file TomlKeyValue get_next_value(); - const TomlLexer& lexer() const; + const TomlLexer& lexer() const { return m_lexer; } }; struct TomlValue diff --git a/tools/dump_hirfile/Makefile b/tools/dump_hirfile/Makefile new file mode 100644 index 00000000..c0ae0513 --- /dev/null +++ b/tools/dump_hirfile/Makefile @@ -0,0 +1,56 @@ +# +ifeq ($(OS),Windows_NT) + EXESUF ?= .exe +endif +EXESUF ?= + +V ?= @ + +OBJDIR := .obj/ + +BIN := ../bin/dump_hirfile$(EXESUF) +OBJS := main.o +OBJS += debug.o rc_string.o span.o ident.o +OBJS += parse/parseerror.o # Why is this needed? ast/path.cpp uses it in binding +OBJS += hir/hir.o hir/type.o hir/deserialise.o hir/serialise_lowlevel.o +OBJS += hir/crate_ptr.o hir/generic_params.o hir/path.o hir/pattern.o hir/expr_ptr.o +OBJS += hir/expr.o # Why is this needed? +OBJS += parse/token.o parse/tokentree.o parse/tokenstream.o +OBJS += ast/ast.o ast/expr.o ast/path.o ast/types.o ast/pattern.o +OBJS += mir/mir.o mir/mir_ptr.o mir/dump.o hir/visitor.o +OBJS += macro_rules/mod.o + +LINKFLAGS := -g -lpthread -lz +CXXFLAGS := -Wall -std=c++14 -g -O2 +CXXFLAGS += -I ../common -I ../../src -I ../../src/include + +OBJS := $(OBJS:%=$(OBJDIR)%) + +.PHONY: all clean + +all: $(BIN) + +clean: + rm $(BIN) $(OBJS) + +$(BIN): $(OBJS) ../bin/common_lib.a + @mkdir -p $(dir $@) + @echo [CXX] -o $@ + $V$(CXX) -o $@ $(OBJS) ../bin/common_lib.a $(LINKFLAGS) + +$(OBJDIR)%.o: %.cpp + @mkdir -p $(dir $@) + @echo [CXX] $< + $V$(CXX) -o $@ -c $< $(CXXFLAGS) -MMD -MP -MF $@.dep + +$(OBJDIR)%.o: ../../src/%.cpp + @mkdir -p $(dir $@) + @echo [CXX] $< + $V$(CXX) -o $@ -c $< $(CXXFLAGS) -MMD -MP -MF $@.dep + +../bin/common_lib.a: $(wildcard ../common/*.* ../common/Makefile) + make -C ../common + +-include $(OBJS:%.o=%.o.dep) + + diff --git a/tools/dump_hirfile/main.cpp b/tools/dump_hirfile/main.cpp new file mode 100644 index 00000000..47436950 --- /dev/null +++ b/tools/dump_hirfile/main.cpp @@ -0,0 +1,327 @@ +#include <hir/hir.hpp> +#include <hir/item_path.hpp> +#include <hir/main_bindings.hpp> +#include <macro_rules/macro_rules.hpp> +#include <mir/mir.hpp> +#include <mir/operations.hpp> // MIR_Dump_Fcn + +int g_debug_indent_level = 0; + +struct Args +{ + Args(int argc, const char* const argv[]); + + ::std::string infile; +}; + +struct Dumper +{ + struct Filters { + struct Types { + bool macros = true; + bool functions = true; + bool statics = true; + bool types = true; + bool traits = true; + } types; + bool public_only = false; + bool no_body = false; + } filters; + + void dump_crate(const char* name, const ::HIR::Crate& crate) const; + void dump_module(::HIR::ItemPath ip, const ::HIR::Publicity& pub, const ::HIR::Module& mod) const; + void dump_function(::HIR::ItemPath ip, const ::HIR::Publicity& pub, const ::HIR::Function& fcn, int indent=0) const; + void dump_trait(::HIR::ItemPath ip, const ::HIR::Publicity& pub, const ::HIR::Trait& trait, int indent=0) const; +}; + +int main(int argc, const char* argv[]) +{ + Args args(argc, argv); + Dumper dumper; + + dumper.filters.types.functions = true; + + auto hir = HIR_Deserialise(args.infile); + dumper.dump_crate("", *hir); +} +namespace { + template<typename T, typename Fcn> + void dump_impl_group(const ::HIR::Crate::ImplGroup<T>& ig, Fcn cb) + { + for(const auto& named_il : ig.named) + { + for(const auto& impl : named_il.second) + { + cb(*impl); + } + } + for(const auto& impl : ig.non_named) + { + cb(*impl); + } + for(const auto& impl : ig.generic) + { + cb(*impl); + } + } +} +void Dumper::dump_crate(const char* name, const ::HIR::Crate& crate) const +{ + // Dump macros + for(const auto& mac : crate.m_exported_macros) + { + ::std::cout << "macro_rules! " << mac.first << "{" << std::endl; + for(const auto& arm : mac.second->m_rules) + { + ::std::cout << " ("; + for(const auto& pat : arm.m_pattern) + { + TU_MATCH_HDRA( (pat), {) + TU_ARMA(End, e) + ::std::cout << " EOS"; + TU_ARMA(LoopStart, e) + ::std::cout << " ("; + TU_ARMA(LoopNext, e) + ::std::cout << " ^"; + TU_ARMA(LoopEnd, e) + ::std::cout << " )"; + TU_ARMA(Jump, e) + ::std::cout << " <" << e.jump_target; + TU_ARMA(ExpectTok, e) + ::std::cout << " =" << e; + TU_ARMA(ExpectPat, e) + ::std::cout << " " << e.idx << "=" << e.type; + TU_ARMA(If, e) { + ::std::cout << " ?" << (e.is_equal ? "" : "!") << "{"; + for(const auto& ent : e.ents) { + if(ent.ty == MacroPatEnt::PAT_TOKEN) + ::std::cout << " =" << ent.tok; + else + ::std::cout << " " << ent.ty; + } + ::std::cout << "}->" << e.jump_target; + } + } + } + ::std::cout << " ) => {\n"; + // TODO: Macro expansion + ::std::cout << " }\n"; + } + ::std::cout << "}\n"; + ::std::cout << ::std::endl; + } + + this->dump_module(::HIR::ItemPath(name), ::HIR::Publicity::new_global(), crate.m_root_module); + + for(const auto& i : crate.m_trait_impls) + { + dump_impl_group(i.second, [&](const ::HIR::TraitImpl& ti) { + auto root_ip = ::HIR::ItemPath(ti.m_type, i.first, ti.m_trait_args); + ::std::cout << "impl" << ti.m_params.fmt_args() << " " << i.first << ti.m_trait_args << " for " << ti.m_type << "\n"; + ::std::cout << " where" << ti.m_params.fmt_bounds() << "\n"; + ::std::cout << "{" << ::std::endl; + if( this->filters.types.functions ) + { + for(const auto& m : ti.m_methods) + { + this->dump_function(root_ip + m.first, ::HIR::Publicity::new_global(), m.second.data, 1); + } + } + ::std::cout << "}" << ::std::endl; + }); + } + + dump_impl_group(crate.m_type_impls, [&](const auto& i) { + auto root_ip = ::HIR::ItemPath(i.m_type); + ::std::cout << "impl" << i.m_params.fmt_args() << " " << i.m_type << "\n"; + ::std::cout << " where" << i.m_params.fmt_bounds() << "\n"; + ::std::cout << "{" << ::std::endl; + if( this->filters.types.functions ) + { + for(const auto& m : i.m_methods) + { + this->dump_function(root_ip + m.first, ::HIR::Publicity::new_global(), m.second.data, 1); + } + } + ::std::cout << "}" << ::std::endl; + }); +} +void Dumper::dump_module(::HIR::ItemPath ip, const ::HIR::Publicity& pub, const ::HIR::Module& mod) const +{ + if( filters.public_only && !pub.is_global() ) + { + return ; + } + ::std::cout << "// mod " << ip << ::std::endl; + for(const auto& i : mod.m_mod_items) + { + auto sub_ip = ip + i.first; + //::std::cout << "// " << i.second->ent.tagstr() << " " << sub_ip << "\n"; + TU_MATCH_HDRA( (i.second->ent), {) + TU_ARMA(Module, e) { + this->dump_module(sub_ip, i.second->publicity, e); + } + TU_ARMA(Import, e) { + //this->dump_mod_import(sub_ip, e); + } + TU_ARMA(TypeAlias, e) { + //this->dump_type_alias(sub_ip, e); + } + TU_ARMA(ExternType, e) { + //this->dump_ext_type(sub_ip, e); + } + TU_ARMA(Enum, e) { + //this->dump_enum(sub_ip, e); + } + TU_ARMA(Struct, e) { + //this->dump_enum(sub_ip, e); + } + TU_ARMA(Union, e) { + //this->dump_trait(sub_ip, e); + } + TU_ARMA(Trait, e) { + this->dump_trait(sub_ip, i.second->publicity, e); + } + } + } + for(const auto& i : mod.m_value_items) + { + auto sub_ip = ip + i.first; + //::std::cout << "// " << i.second->ent.tagstr() << " " << sub_ip << "\n"; + TU_MATCH_HDRA( (i.second->ent), {) + TU_ARMA(Import, e) { + //this->dump_val_import(sub_ip, e); + } + TU_ARMA(Constant, e) { + //this->dump_constant(sub_ip, e); + } + TU_ARMA(Static, e) { + //this->dump_constant(sub_ip, e); + } + TU_ARMA(StructConstant, e) { + //this->dump_constant(sub_ip, e); + } + TU_ARMA(StructConstructor, e) { + //this->dump_constant(sub_ip, e); + } + TU_ARMA(Function, e) { + this->dump_function(sub_ip, i.second->publicity, e); + } + } + } +} +void Dumper::dump_function(::HIR::ItemPath ip, const ::HIR::Publicity& pub, const ::HIR::Function& fcn, int nindent/*=0*/) const +{ + auto indent = RepeatLitStr { " ", nindent }; + if( !this->filters.types.functions ) { + return ; + } + if( filters.public_only && !pub.is_global() ) { + return ; + } + ::std::cout << indent << "fn " << ip << fcn.m_params.fmt_args() << "("; + ::std::cout << " )"; + if( fcn.m_code.m_mir ) + { + ::std::cout << "\n"; + ::std::cout << indent << "{\n"; + if( filters.no_body ) { + ::std::cout << indent << "...\n"; + } + else { + MIR_Dump_Fcn(::std::cout, *fcn.m_code.m_mir, nindent+1); + } + ::std::cout << indent << "}\n"; + ::std::cout << ::std::endl; + } + else + { + ::std::cout << ";" << ::std::endl; + } +} +void Dumper::dump_trait(::HIR::ItemPath ip, const ::HIR::Publicity& pub, const ::HIR::Trait& trait, int nindent/*=0*/) const +{ + auto indent = RepeatLitStr { " ", nindent }; + if( !this->filters.types.functions ) { + return ; + } + if( !filters.public_only && !pub.is_global() ) { + return ; + } + ::std::cout << indent << "trait " << ip << trait.m_params.fmt_args() << "\n"; + ::std::cout << indent << "{\n"; + auto indent2 = RepeatLitStr { " ", nindent+1 }; + // ... + ::std::cout << indent << "}\n"; + ::std::cout << ::std::endl; +} + +bool debug_enabled() +{ + return false; +} +::std::ostream& debug_output(int indent, const char* function) +{ + return ::std::cout << "- " << RepeatLitStr { " ", indent } << function << ": "; +} + +Args::Args(int argc, const char* const argv[]) +{ + this->infile = argv[1]; +} + +// TODO: This is copy-pasted from src/main.cpp, should live somewhere better +::std::ostream& operator<<(::std::ostream& os, const FmtEscaped& x) +{ + os << ::std::hex; + for(auto s = x.s; *s != '\0'; s ++) + { + switch(*s) + { + case '\0': os << "\\0"; break; + case '\n': os << "\\n"; break; + case '\\': os << "\\\\"; break; + case '"': os << "\\\""; break; + default: + uint8_t v = *s; + if( v < 0x80 ) + { + if( v < ' ' || v > 0x7F ) + os << "\\u{" << ::std::hex << (unsigned int)v << "}"; + else + os << v; + } + else if( v < 0xC0 ) + ; + else if( v < 0xE0 ) + { + uint32_t val = (uint32_t)(v & 0x1F) << 6; + v = (uint8_t)*++s; if( (v & 0xC0) != 0x80 ) { s--; continue ; } val |= (uint32_t)v << 6; + os << "\\u{" << ::std::hex << val << "}"; + } + else if( v < 0xF0 ) + { + uint32_t val = (uint32_t)(v & 0x0F) << 12; + v = (uint8_t)*++s; if( (v & 0xC0) != 0x80 ) { s--; continue ; } val |= (uint32_t)v << 12; + v = (uint8_t)*++s; if( (v & 0xC0) != 0x80 ) { s--; continue ; } val |= (uint32_t)v << 6; + os << "\\u{" << ::std::hex << val << "}"; + } + else if( v < 0xF8 ) + { + uint32_t val = (uint32_t)(v & 0x07) << 18; + v = (uint8_t)*++s; if( (v & 0xC0) != 0x80 ) { s--; continue ; } val |= (uint32_t)v << 18; + v = (uint8_t)*++s; if( (v & 0xC0) != 0x80 ) { s--; continue ; } val |= (uint32_t)v << 12; + v = (uint8_t)*++s; if( (v & 0xC0) != 0x80 ) { s--; continue ; } val |= (uint32_t)v << 6; + os << "\\u{" << ::std::hex << val << "}"; + } + break; + } + } + os << ::std::dec; + return os; +} + +MIR::EnumCachePtr::~EnumCachePtr() +{ + assert(!this->p); +} diff --git a/tools/minicargo/Makefile b/tools/minicargo/Makefile index 363ef4b9..862f6b3c 100644 --- a/tools/minicargo/Makefile +++ b/tools/minicargo/Makefile @@ -13,7 +13,7 @@ V ?= @ OBJDIR := .obj/ BIN := ../bin/minicargo$(EXESUF) -OBJS := main.o build.o manifest.o repository.o +OBJS := main.o build.o manifest.o repository.o cfg.o LINKFLAGS := -g -lpthread CXXFLAGS := -Wall -std=c++14 -g -O2 diff --git a/tools/minicargo/build.cpp b/tools/minicargo/build.cpp index 53fa1a16..efacb6f8 100644 --- a/tools/minicargo/build.cpp +++ b/tools/minicargo/build.cpp @@ -24,6 +24,7 @@ extern int _putenv_s(const char*, const char*); #include <vector> #include <algorithm> #include <sstream> // stringstream +#include <fstream> // ifstream #include <cstdlib> // setenv #ifndef DISABLE_MULTITHREAD # include <thread> @@ -52,8 +53,10 @@ extern int _putenv_s(const char*, const char*); #ifdef _WIN32 # define EXESUF ".exe" +# define DLLSUF ".dll" #else # define EXESUF "" +# define DLLSUF ".so" #endif #include <target_detect.h> // tools/common/target_detect.h #define HOST_TARGET DEFAULT_TARGET_NAME @@ -61,17 +64,19 @@ extern int _putenv_s(const char*, const char*); /// Class abstracting access to the compiler class Builder { - BuildOptions m_opts; + const BuildOptions& m_opts; ::helpers::path m_compiler_path; + size_t m_total_targets; + mutable size_t m_targets_built; #ifndef _WIN32 mutable ::std::mutex chdir_mutex; #endif public: - Builder(BuildOptions opts); + Builder(const BuildOptions& opts, size_t total_targets); - bool build_target(const PackageManifest& manifest, const PackageTarget& target, bool is_for_host) const; - bool build_library(const PackageManifest& manifest, bool is_for_host) const; + bool build_target(const PackageManifest& manifest, const PackageTarget& target, bool is_for_host, size_t index) const; + bool build_library(const PackageManifest& manifest, bool is_for_host, size_t index) const; ::helpers::path build_build_script(const PackageManifest& manifest, bool is_for_host, bool* out_is_rebuilt) const; private: @@ -217,6 +222,18 @@ BuildList::BuildList(const PackageManifest& manifest, const BuildOptions& opts): { b.m_list.push_back({ &manifest, !cross_compiling, 0 }); } + if( opts.mode != BuildOptions::Mode::Normal) + { + for(const auto& dep : manifest.dev_dependencies()) + { + if( dep.is_disabled() ) + { + continue ; + } + DEBUG(manifest.name() << ": Dependency " << dep.name()); + b.add_package(dep.get_package(), 1, !opts.build_script_overrides.is_valid(), !cross_compiling); + } + } // TODO: Add the binaries too? // - They need slightly different treatment. @@ -260,7 +277,7 @@ BuildList::BuildList(const PackageManifest& manifest, const BuildOptions& opts): bool BuildList::build(BuildOptions opts, unsigned num_jobs) { bool include_build = !opts.build_script_overrides.is_valid(); - Builder builder { ::std::move(opts) }; + Builder builder { opts, m_list.size() }; // Pre-count how many dependencies are remaining for each package struct BuildState @@ -413,7 +430,7 @@ bool BuildList::build(BuildOptions opts, unsigned num_jobs) } DEBUG("Thread " << my_idx << ": Starting " << cur << " - " << list[cur].package->name()); - if( ! builder->build_library(*list[cur].package, list[cur].is_host) ) + if( ! builder->build_library(*list[cur].package, list[cur].is_host, cur) ) { queue.failure = true; queue.signal_all(); @@ -474,7 +491,7 @@ bool BuildList::build(BuildOptions opts, unsigned num_jobs) { auto cur = state.get_next(); - if( ! builder.build_library(*m_list[cur].package, m_list[cur].is_host) ) + if( ! builder.build_library(*m_list[cur].package, m_list[cur].is_host, cur) ) { return false; } @@ -488,7 +505,7 @@ bool BuildList::build(BuildOptions opts, unsigned num_jobs) { auto cur = state.get_next(); - if( ! builder.build_library(*m_list[cur].package, m_list[cur].is_host) ) + if( ! builder.build_library(*m_list[cur].package, m_list[cur].is_host, cur) ) { return false; } @@ -532,14 +549,26 @@ bool BuildList::build(BuildOptions opts, unsigned num_jobs) } // Now that all libraries are done, build the binaries (if present) - return this->m_root_manifest.foreach_binaries([&](const auto& bin_target) { - return builder.build_target(this->m_root_manifest, bin_target, /*is_for_host=*/false); - }); + switch(opts.mode) + { + case BuildOptions::Mode::Normal: + return this->m_root_manifest.foreach_binaries([&](const auto& bin_target) { + return builder.build_target(this->m_root_manifest, bin_target, /*is_for_host=*/false, ~0u); + }); + case BuildOptions::Mode::Test: + // TODO: What about unit tests? + return this->m_root_manifest.foreach_ty(PackageTarget::Type::Test, [&](const auto& test_target) { + return builder.build_target(this->m_root_manifest, test_target, /*is_for_host=*/true, ~0u); + }); + } + throw "unreachable"; } -Builder::Builder(BuildOptions opts): - m_opts(::std::move(opts)) +Builder::Builder(const BuildOptions& opts, size_t total_targets): + m_opts(opts), + m_total_targets(total_targets), + m_targets_built(0) { if( const char* override_path = getenv("MRUSTC_PATH") ) { m_compiler_path = override_path; @@ -614,16 +643,44 @@ Builder::Builder(BuildOptions opts): switch(target.m_type) { case PackageTarget::Type::Lib: - if(crate_type) { - *crate_type = target.m_is_proc_macro ? "proc-macro" : "rlib"; + switch( target.m_crate_types.size() > 0 + ? target.m_crate_types.front() + : (target.m_is_proc_macro + ? PackageTarget::CrateType::proc_macro + : PackageTarget::CrateType::rlib + ) + ) + { + case PackageTarget::CrateType::proc_macro: + if(crate_type) *crate_type = "proc-macro"; + outfile /= ::format("lib", target.m_name, crate_suffix, "-plugin" EXESUF); + break; + case PackageTarget::CrateType::dylib: + if( getenv("MINICARGO_DYLIB") ) + { + // TODO: Enable this once mrustc can set rpath or absolute paths + if(crate_type) *crate_type = "dylib"; + outfile /= ::format("lib", target.m_name, crate_suffix, DLLSUF); + break; + } + case PackageTarget::CrateType::rlib: + if(crate_type) *crate_type = "rlib"; + outfile /= ::format("lib", target.m_name, crate_suffix, ".rlib"); + break; + default: + throw ""; } - outfile /= ::format("lib", target.m_name, crate_suffix, ".hir"); break; case PackageTarget::Type::Bin: if(crate_type) *crate_type = "bin"; outfile /= ::format(target.m_name, EXESUF); break; + case PackageTarget::Type::Test: + if(crate_type) + *crate_type = "bin"; + outfile /= ::format(target.m_name, EXESUF); + break; default: throw ::std::runtime_error("Unknown target type being built"); } @@ -632,11 +689,117 @@ Builder::Builder(BuildOptions opts): return outfile; } -bool Builder::build_target(const PackageManifest& manifest, const PackageTarget& target, bool is_for_host) const +namespace { + ::std::map< ::std::string, ::std::vector<helpers::path> > load_depfile(const helpers::path& depfile_path) + { + ::std::map< ::std::string, ::std::vector<helpers::path> > rv; + ::std::ifstream ifp(depfile_path); + if( ifp.good() ) + { + // Load space-separated (backslash-escaped) paths + struct Lexer { + ::std::ifstream ifp; + char m_c; + + Lexer(::std::ifstream ifp) + :ifp(::std::move(ifp)) + ,m_c(0) + { + nextc(); + } + + bool nextc() { + int v = ifp.get(); + if( v == EOF ) { + m_c = '\0'; + return false; + } + else { + m_c = (char)v; + return true; + } + } + ::std::string get_token() { + auto t = get_token_int(); + //DEBUG("get_token '" << t << "'"); + return t; + } + ::std::string get_token_int() { + if( ifp.eof() ) + return ""; + while( m_c == ' ' ) + { + if( !nextc() ) + return ""; + } + if( m_c == '\n' ) { + nextc(); + return "\n"; + } + if( m_c == '\t' ) { + nextc(); + return "\t"; + } + ::std::string rv; + do { + if( m_c == '\\' ) + { + nextc(); + if( m_c == ' ' ) { + rv += m_c; + } + else if( m_c == ':' ) { + rv += m_c; + } + // HACK: Only spaces are escaped this way? + else { + rv += '\\'; + rv += m_c; + } + } + else + { + rv += m_c; + } + } while( nextc() && m_c != ' ' && m_c != ':' && m_c != '\n' ); + return rv; + } + } lexer(::std::move(ifp)); + + // Look for <string> ":" [<string>]* "\n" + do { + auto t = lexer.get_token(); + if( t == "" ) + break; + if( t == "\n" ) + continue ; + + auto v = rv.insert(::std::make_pair(t, ::std::vector<helpers::path>())); + auto& list = v.first->second; + auto target = t; + t = lexer.get_token(); + assert(t == ":"); + + do { + t = lexer.get_token(); + if( t == "\n" || t == "" ) + break ; + list.push_back(t); + } while(1); + } while(1); + } + return rv; + } +} + +bool Builder::build_target(const PackageManifest& manifest, const PackageTarget& target, bool is_for_host, size_t index) const { const char* crate_type; ::std::string crate_suffix; auto outfile = this->get_crate_path(manifest, target, is_for_host, &crate_type, &crate_suffix); + auto depfile = outfile + ".d"; + + size_t this_target_idx = (index != ~0u ? m_targets_built++ : ~0u); // TODO: Determine if it needs re-running // Rerun if: @@ -658,22 +821,58 @@ bool Builder::build_target(const PackageManifest& manifest, const PackageTarget& DEBUG("Building " << outfile << " - Older than mrustc ( " << ts_result << " < " << Timestamp::for_file(m_compiler_path) << ")"); } else { - // TODO: Check dependencies. (from depfile) - // Don't rebuild (no need to) - DEBUG("Not building " << outfile << " - not out of date"); - return true; + // Check dependencies. (from depfile) + auto depfile_ents = load_depfile(depfile); + auto it = depfile_ents.find(outfile); + bool has_new_file = false; + if( it != depfile_ents.end() ) + { + for(const auto& f : it->second) + { + auto dep_ts = Timestamp::for_file(f); + if( ts_result < dep_ts ) + { + has_new_file = true; + DEBUG("Rebuilding " << outfile << ", older than " << f); + break; + } + } + } + + if( !has_new_file ) + { + // Don't rebuild (no need to) + DEBUG("Not building " << outfile << " - not out of date"); + return true; + } } for(const auto& cmd : manifest.build_script_output().pre_build_commands) { // TODO: Run commands specified by build script (override) + TODO("Run command `" << cmd << "` from build script override"); } - ::std::cout << "BUILDING " << target.m_name << " from " << manifest.name() << " v" << manifest.version() << " with features [" << manifest.active_features() << "]" << ::std::endl; + { + // TODO: Determine what number and total targets there are + if( index != ~0u ) { + //::std::cout << "(" << index << "/" << m_total_targets << ") "; + ::std::cout << "(" << this_target_idx << "/" << m_total_targets << ") "; + } + ::std::cout << "BUILDING "; + if(target.m_name != manifest.name()) + ::std::cout << target.m_name << " from "; + ::std::cout << manifest.name() << " v" << manifest.version(); + if( !manifest.active_features().empty() ) + ::std::cout << " with features [" << manifest.active_features() << "]"; + ::std::cout << ::std::endl; + } StringList args; args.push_back(::helpers::path(manifest.manifest_path()).parent() / ::helpers::path(target.m_path)); + args.push_back("-o"); args.push_back(outfile); args.push_back("--crate-name"); args.push_back(target.m_name.c_str()); args.push_back("--crate-type"); args.push_back(crate_type); + args.push_back("-C"); args.push_back(format("emit-depfile=",depfile)); if( !crate_suffix.empty() ) { args.push_back("--crate-tag"); args.push_back(crate_suffix.c_str() + 1); } @@ -699,7 +898,11 @@ bool Builder::build_target(const PackageManifest& manifest, const PackageTarget& args.push_back("-C"); args.push_back("codegen-type=monomir"); } - args.push_back("-o"); args.push_back(outfile); + for(const auto& d : m_opts.lib_search_dirs) + { + args.push_back("-L"); + args.push_back(d.str().c_str()); + } args.push_back("-L"); args.push_back(this->get_output_dir(is_for_host).str()); for(const auto& dir : manifest.build_script_output().rustc_link_search) { args.push_back("-L"); args.push_back(dir.second.c_str()); @@ -725,6 +928,10 @@ bool Builder::build_target(const PackageManifest& manifest, const PackageTarget& args.push_back("--extern"); args.push_back(::format(m.get_library().m_name, "=", path)); } + if( target.m_type == PackageTarget::Type::Test ) + { + args.push_back("--test"); + } for(const auto& dep : manifest.dependencies()) { if( ! dep.is_disabled() ) @@ -735,10 +942,18 @@ bool Builder::build_target(const PackageManifest& manifest, const PackageTarget& args.push_back(::format(m.get_library().m_name, "=", path)); } } - for(const auto& d : m_opts.lib_search_dirs) + if( target.m_type == PackageTarget::Type::Test ) { - args.push_back("-L"); - args.push_back(d.str().c_str()); + for(const auto& dep : manifest.dev_dependencies()) + { + if( ! dep.is_disabled() ) + { + const auto& m = dep.get_package(); + auto path = this->get_crate_path(m, m.get_library(), is_for_host, nullptr, nullptr); + args.push_back("--extern"); + args.push_back(::format(m.get_library().m_name, "=", path)); + } + } } // TODO: Environment variables (rustc_env) @@ -867,13 +1082,13 @@ bool Builder::build_target(const PackageManifest& manifest, const PackageTarget& StringListKV env; env.push_back("CARGO_MANIFEST_DIR", manifest.directory().to_absolute()); //env.push_back("CARGO_MANIFEST_LINKS", manifest.m_links); - //for(const auto& feat : manifest.m_active_features) - //{ - // ::std::string fn = "CARGO_FEATURE_"; - // for(char c : feat) - // fn += c == '-' ? '_' : tolower(c); - // env.push_back(fn, manifest.m_links); - //} + for(const auto& feat : manifest.active_features()) + { + ::std::string fn = "CARGO_FEATURE_"; + for(char c : feat) + fn += c == '-' ? '_' : toupper(c); + env.push_back(fn, "1"); + } //env.push_back("CARGO_CFG_RELEASE", ""); env.push_back("OUT_DIR", out_dir); env.push_back("TARGET", m_opts.target_name ? m_opts.target_name : HOST_TARGET); @@ -882,6 +1097,11 @@ bool Builder::build_target(const PackageManifest& manifest, const PackageTarget& env.push_back("OPT_LEVEL", "2"); env.push_back("DEBUG", "0"); env.push_back("PROFILE", "release"); + // TODO: All cfg(foo_bar) become CARGO_CFG_FOO_BAR + env.push_back("CARGO_CFG_TARGET_POINTER_WIDTH", "32"); + // - Needed for `regex`'s build script, make mrustc pretend to be rustc + env.push_back("RUSTC", this->m_compiler_path); + for(const auto& dep : manifest.dependencies()) { if( ! dep.is_disabled() ) @@ -922,7 +1142,7 @@ bool Builder::build_target(const PackageManifest& manifest, const PackageTarget& return out_file; } -bool Builder::build_library(const PackageManifest& manifest, bool is_for_host) const +bool Builder::build_library(const PackageManifest& manifest, bool is_for_host, size_t index) const { if( manifest.build_script() != "" ) { @@ -948,7 +1168,7 @@ bool Builder::build_library(const PackageManifest& manifest, bool is_for_host) c } } - return this->build_target(manifest, manifest.get_library(), is_for_host); + return this->build_target(manifest, manifest.get_library(), is_for_host, index); } bool Builder::spawn_process_mrustc(const StringList& args, StringListKV env, const ::helpers::path& logfile) const { @@ -986,6 +1206,7 @@ bool Builder::spawn_process(const char* exe_name, const StringList& args, const #else for(auto kv : env) { + DEBUG("putenv " << kv.first << "=" << kv.second); _putenv_s(kv.first, kv.second); } #endif diff --git a/tools/minicargo/build.h b/tools/minicargo/build.h index bba22964..83af88d6 100644 --- a/tools/minicargo/build.h +++ b/tools/minicargo/build.h @@ -20,7 +20,15 @@ struct BuildOptions ::helpers::path build_script_overrides; ::std::vector<::helpers::path> lib_search_dirs; bool emit_mmir = false; - const char* target_name = nullptr; // if null, host is used + const char* target_name = nullptr; // if null, host is used + enum class Mode { + /// Build the binary/library + Normal, + /// Build tests + Test, + /// Build examples + Examples, + } mode = Mode::Normal; }; class BuildList diff --git a/tools/minicargo/cfg.cpp b/tools/minicargo/cfg.cpp new file mode 100644 index 00000000..85bff327 --- /dev/null +++ b/tools/minicargo/cfg.cpp @@ -0,0 +1,269 @@ +/* + * mrustc "minicargo" (minimal cargo clone) + * - By John Hodge (Mutabah) + * + * cfg.cpp + * - Handling of target configuration (in manifest nodes) + */ +#include <iostream> // cerr +#include "debug.h" +#include <cassert> +#include <algorithm> +#include <string> +#include <cstring> +#include "cfg.hpp" + +// TODO: Extract this from the target at runtime (by invoking the compiler on the passed target) +#ifdef _WIN32 +//# define TARGET_NAME "i586-windows-msvc" +# define CFG_UNIX false +# define CFG_WINDOWS true +#elif defined(__NetBSD__) +//# define TARGET_NAME "x86_64-unknown-netbsd" +# define CFG_UNIX true +# define CFG_WINDOWS false +#else +//# define TARGET_NAME "x86_64-unknown-linux-gnu" +# define CFG_UNIX true +# define CFG_WINDOWS false +#endif + +class CfgParseLexer +{ +public: + class Tok + { + friend class CfgParseLexer; + public: + enum Type { + EndOfStream, + Operator, + Ident, + String, + }; + private: + Type m_ty; + const char* s; + const char* e; + ::std::string m_val; + Tok(): + m_ty(EndOfStream), s(nullptr),e(nullptr) + { + } + Tok(const char* s): + m_ty(Operator), s(s), e(s+1), m_val() + { + } + Tok(const char* s, const char* e): + m_ty(Ident), s(s), e(e), m_val() + { + } + Tok(const char* s, const char* e, ::std::string val): + m_ty(String), s(s), e(e), m_val(::std::move(val)) + { + } + public: + bool operator==(char c) const { + return (m_ty == Operator && *s == c); + } + bool operator!=(char c) const { return !(*this == c); } + bool operator==(const char* v) const { + return strlen(v) == static_cast<unsigned>(e - s) && memcmp(s, v, e-s) == 0; + } + bool operator!=(const char* v) const { return !(*this == v); } + + const Type ty() const { return m_ty; } + const ::std::string& str() const { + return m_val; + } + ::std::string to_string() const { + return ::std::string(s, e); + } + }; +private: + const char* m_pos; + Tok m_cur; + +public: + CfgParseLexer(const char* s): + m_pos(s), + m_cur(nullptr,nullptr) + { + consume(); + } + const Tok& cur() const { + return m_cur; + } + + Tok consume() { + auto rv = m_cur; + m_cur = get_next(); + //::std::cout << "consume: " << rv.to_string() << " => " << m_cur.to_string() << ::std::endl; + return rv; + } + bool consume_if(char c) { + if( cur() == c ) { + consume(); + return true; + } + else { + return false; + } + } + bool consume_if(const char* s) { + if( cur() == s ) { + consume(); + return true; + } + else { + return false; + } + } +private: + Tok get_next(); +}; + +struct CfgChecker +{ + const char* target_env; + const char* target_os; + const char* target_arch; + + bool check_cfg(CfgParseLexer& p) const; +}; + +CfgChecker gCfgChecker { + (CFG_WINDOWS ? "msvc" : "gnu"), + (CFG_WINDOWS ? "windows" : "linux"), + "x86" + }; + +CfgParseLexer::Tok CfgParseLexer::get_next() +{ + while(*m_pos == ' ') + m_pos ++; + if(*m_pos == 0) + return Tok(); + switch(*m_pos) + { + case '(': case ')': + case ',': case '=': + return Tok(m_pos++); + case '"': { + ::std::string str; + auto s = m_pos; + m_pos ++; + while( *m_pos != '"' ) + { + if( *m_pos == '\\' ) + { + TODO("Escape sequences in cfg parser"); + } + str += *m_pos; + m_pos ++; + } + m_pos ++; + return Tok(s, m_pos, str); } + default: + if( isalnum(*m_pos) || *m_pos == '_' ) + { + auto s = m_pos; + while(isalnum(*m_pos) || *m_pos == '_') + m_pos ++; + return Tok(s, m_pos); + } + else + { + throw ::std::runtime_error(format("Unexpected character in cfg() - ", *m_pos)); + } + } +} + +bool Cfg_Check(const char* cfg_string) +{ + CfgParseLexer p { cfg_string + 4 }; + + if( gCfgChecker.target_os == nullptr ) + { + // TODO: If the checker isn't initialised, invoke the compiler and ask it to dump the current target + // - It's pre-initialised above currently + } + + bool success = gCfgChecker.check_cfg(p); + if( !p.consume_if(")") ) + throw ::std::runtime_error(format("Expected ')' after cfg condition - got", p.cur().to_string())); + return success; +} + +bool CfgChecker::check_cfg(CfgParseLexer& p) const +{ + auto name = p.consume(); + if( name.ty() != CfgParseLexer::Tok::Ident ) + throw ::std::runtime_error("Expected an identifier"); + // Combinators + if( p.consume_if('(') ) { + bool rv; + if( false ) { + } + else if( name == "not" ) { + rv = !check_cfg(p); + } + else if( name == "all" ) { + rv = true; + do + { + rv &= check_cfg(p); + } while(p.consume_if(',')); + } + else if( name == "any" ) { + rv = false; + do + { + rv |= check_cfg(p); + } while(p.consume_if(',')); + } + else { + TODO("Unknown fragment in cfg - " << name.to_string()); + } + if( !p.consume_if(')') ) + throw ::std::runtime_error("Expected ')' after combinator content"); + return rv; + } + // Values + else if( p.consume_if('=') ) { + auto t = p.consume(); + if( t.ty() != CfgParseLexer::Tok::String ) + throw ::std::runtime_error("Expected a string after `=`"); + const auto& val = t.str(); + + if( false ) { + } + else if( name == "target_env" ) + return val == this->target_env; + else if( name == "target_os" ) + return val == this->target_os; + else if( name == "target_arch" ) + return val == this->target_arch; + else { + TODO("Unknown fragment in cfg - " << name.to_string()); + } + } + // Flags + else { + if( false ) { + } + else if( name == "unix" ) { + return CFG_UNIX; + } + else if( name == "windows" ) { + return CFG_WINDOWS; + } + else if( name == "stage0" ) { + return false; + } + else { + TODO("Unknown fragment in cfg - " << name.to_string()); + } + } + throw ::std::runtime_error("Hit end of check_cfg"); +} diff --git a/tools/minicargo/cfg.hpp b/tools/minicargo/cfg.hpp new file mode 100644 index 00000000..907c8079 --- /dev/null +++ b/tools/minicargo/cfg.hpp @@ -0,0 +1,9 @@ +/* + * mrustc "minicargo" (minimal cargo clone) + * - By John Hodge (Mutabah) + * + * cfg.cpp + * - Handling of target configuration (in manifest nodes) + */ +#pragma once +extern bool Cfg_Check(const char* cfg_string); diff --git a/tools/minicargo/main.cpp b/tools/minicargo/main.cpp index ec6b8b45..4e929653 100644 --- a/tools/minicargo/main.cpp +++ b/tools/minicargo/main.cpp @@ -44,6 +44,11 @@ struct ProgramOptions // Pause for user input before quitting (useful for MSVC debugging) bool pause_before_quit = false; + /// Build and run tests? + bool test = false; + + ::std::vector<::std::string> features; + int parse(int argc, const char* argv[]); void usage() const; void help() const; @@ -97,10 +102,11 @@ int main(int argc, const char* argv[]) Debug_SetPhase("Load Root"); auto dir = ::helpers::path(opts.directory ? opts.directory : "."); auto m = PackageManifest::load_from_toml( dir / "Cargo.toml" ); + m.set_features(opts.features, opts.features.empty()); // 2. Load all dependencies Debug_SetPhase("Load Dependencies"); - m.load_dependencies(repo, !bs_override_dir.is_valid()); + m.load_dependencies(repo, !bs_override_dir.is_valid(), /*include_dev=*/opts.test); // 3. Build dependency tree and build program. BuildOptions build_opts; @@ -111,6 +117,11 @@ int main(int argc, const char* argv[]) build_opts.target_name = opts.target; for(const auto* d : opts.lib_search_dirs) build_opts.lib_search_dirs.push_back( ::helpers::path(d) ); + // Indicate desire to build tests (or examples) instead of the primary target + build_opts.mode = + opts.test ? BuildOptions::Mode::Test : + BuildOptions::Mode::Normal + ; Debug_SetPhase("Enumerate Build"); auto build_list = BuildList(m, build_opts); Debug_SetPhase("Run Build"); @@ -251,9 +262,25 @@ int ProgramOptions::parse(int argc, const char* argv[]) } this->target = argv[++i]; } + else if( ::std::strcmp(arg, "--features") == 0 ) { + if(i+1 == argc) { + ::std::cerr << "Flag " << arg << " takes an argument" << ::std::endl; + return 1; + } + const auto* a = argv[++i]; + while(const char* e = strchr(a, ',')) + { + this->features.push_back( ::std::string(a, e) ); + a = e + 1; + } + this->features.push_back( ::std::string(a) ); + } else if( ::std::strcmp(arg, "--pause") == 0 ) { this->pause_before_quit = true; } + else if( ::std::strcmp(arg, "--test") == 0 ) { + this->test = true; + } else { ::std::cerr << "Unknown flag " << arg << ::std::endl; return 1; diff --git a/tools/minicargo/manifest.cpp b/tools/minicargo/manifest.cpp index e47da1bc..ac9c9cb2 100644 --- a/tools/minicargo/manifest.cpp +++ b/tools/minicargo/manifest.cpp @@ -13,20 +13,15 @@ #include <algorithm> #include <cctype> // toupper #include "repository.h" +#include "cfg.hpp" // TODO: Extract this from the target at runtime (by invoking the compiler on the passed target) #ifdef _WIN32 # define TARGET_NAME "i586-windows-msvc" -# define CFG_UNIX false -# define CFG_WINDOWS true #elif defined(__NetBSD__) # define TARGET_NAME "x86_64-unknown-netbsd" -# define CFG_UNIX true -# define CFG_WINDOWS false #else # define TARGET_NAME "x86_64-unknown-linux-gnu" -# define CFG_UNIX true -# define CFG_WINDOWS false #endif static ::std::vector<::std::shared_ptr<PackageManifest>> g_loaded_manifests; @@ -135,6 +130,16 @@ PackageManifest PackageManifest::load_from_toml(const ::std::string& path) } rv.m_links = key_val.value.as_string(); } + else if( key == "autotests" ) + { + // TODO: Fix the outer makefile so it doesn't need `foo-test` + // to be created. + //rv.m_create_auto_test = key_val.value.as_bool(); + } + else if( key == "autobenches" ) + { + //rv.m_create_auto_bench = key_val.value.as_bool(); + } else { // Unknown value in `package` @@ -184,42 +189,28 @@ PackageManifest PackageManifest::load_from_toml(const ::std::string& path) target_edit_from_kv(*it, key_val, 2); } - else if( section == "dependencies" ) + else if( section == "dependencies" || section == "build-dependencies" || section == "dev-dependencies" ) { + ::std::vector<PackageRef>& dep_list = + section == "dependencies" ? rv.m_dependencies : + section == "build-dependencies" ? rv.m_build_dependencies : + /*section == "dev-dependencies" ? */ rv.m_dev_dependencies /*: + throw ""*/ + ; assert(key_val.path.size() > 1); const auto& depname = key_val.path[1]; // Find/create dependency descriptor - auto it = ::std::find_if(rv.m_dependencies.begin(), rv.m_dependencies.end(), [&](const auto& x) { return x.m_name == depname; }); - bool was_added = (it == rv.m_dependencies.end()); + auto it = ::std::find_if(dep_list.begin(), dep_list.end(), [&](const auto& x) { return x.m_name == depname; }); + bool was_added = (it == dep_list.end()); if( was_added ) { - it = rv.m_dependencies.insert(it, PackageRef{ depname }); + it = dep_list.insert(it, PackageRef{ depname }); } it->fill_from_kv(was_added, key_val, 2); } - else if( section == "build-dependencies" ) - { - assert(key_val.path.size() > 1); - - const auto& depname = key_val.path[1]; - - // Find/create dependency descriptor - auto it = ::std::find_if(rv.m_build_dependencies.begin(), rv.m_build_dependencies.end(), [&](const auto& x) { return x.m_name == depname; }); - bool was_added = (it == rv.m_build_dependencies.end()); - if(was_added) - { - it = rv.m_build_dependencies.insert(it, PackageRef{ depname }); - } - - it->fill_from_kv(was_added, key_val, 2); - } - else if( section == "dev-dependencies" ) - { - // TODO: Developemnt (test/bench) deps - } else if( section == "patch" ) { //const auto& repo = key_val.path[1]; @@ -241,169 +232,7 @@ PackageManifest PackageManifest::load_from_toml(const ::std::string& path) // - It can be a target spec, or a cfg(foo) same as rustc bool success; if( cfg.substr(0, 4) == "cfg(" ) { - class Parser - { - public: - class Tok - { - friend class Parser; - const char* s; - const char* e; - Tok(const char* s, const char* e): - s(s), e(e) - { - } - public: - bool operator==(const char* v) const { - return (strlen(v) == e - s) && memcmp(s, v, e-s) == 0; - } - bool operator!=(const char* v) const { - return (strlen(v) != e - s) || memcmp(s, v, e-s) != 0; - } - ::std::string to_string() const { - return ::std::string(s, e); - } - }; - private: - const char* m_pos; - Tok m_cur; - - public: - Parser(const char* s): - m_pos(s), - m_cur(nullptr,nullptr) - { - consume(); - } - const Tok& cur() const { - return m_cur; - } - - Tok consume() { - auto rv = m_cur; - m_cur = get_next(); - //::std::cout << "consume: " << rv.to_string() << " => " << m_cur.to_string() << ::std::endl; - return rv; - } - bool consume_if(const char* s) { - if( cur() == s ) { - consume(); - return true; - } - else { - return false; - } - } - private: - Tok get_next() { - while(*m_pos == ' ') - m_pos ++; - if(*m_pos == 0) - return Tok { m_pos, m_pos }; - switch(*m_pos) - { - case '(': case ')': - case ',': case '=': - return Tok { m_pos++, m_pos }; - case '"': { - auto s = m_pos; - m_pos ++; - while( *m_pos != '"' ) - { - if( *m_pos == '\\' ) - { - TODO("Escape sequences in cfg parser"); - } - m_pos ++; - } - m_pos ++; - return Tok { s, m_pos }; } - default: - if( isalnum(*m_pos) || *m_pos == '_' ) - { - auto s = m_pos; - while(isalnum(*m_pos) || *m_pos == '_') - m_pos ++; - return Tok { s, m_pos }; - } - else - { - throw ::std::runtime_error(format("Unexpected character in cfg() - ", *m_pos)); - } - } - } - }; - - struct H { - static bool check_cfg(Parser& p) - { - if( p.consume_if("not") ) { - if( !p.consume_if("(") ) - throw ::std::runtime_error("Expected '(' after `not`"); - auto rv = !check_cfg(p); - if( !p.consume_if(")") ) - throw ::std::runtime_error("Expected ')' after `not` content"); - return rv; - } - else if( p.consume_if("all") ) { - if( !p.consume_if("(") ) - throw ::std::runtime_error("Expected '(' after `all`"); - bool rv = true; - do - { - rv &= check_cfg(p); - } while(p.consume_if(",")); - if( !p.consume_if(")") ) - throw ::std::runtime_error("Expected ')' after `all` content"); - return rv; - } - // Strings - else if( p.consume_if("target_os") ) { - if( !p.consume_if("=") ) - throw ::std::runtime_error("Expected '=' after target_os"); - auto t = p.consume(); - if( t == "\"emscripten\"" ) { - return false; - } - else if( t == "\"macos\"" ) { - return false; - } - else { - TODO("Handle target_os string - " << t.to_string()); - } - } - else if( p.consume_if("target_arch") ) { - if( !p.consume_if("=") ) - throw ::std::runtime_error("Expected '=' after target"); - auto t = p.consume(); - if( t == "\"wasm32\"" ) { - return false; - } - else{ - TODO("Handle target_arch string - " << t.to_string()); - } - } - // Flags - else if( p.consume_if("unix") ) { - return CFG_UNIX; - } - else if( p.consume_if("windows") ) { - return CFG_WINDOWS; - } - else if( p.consume_if("stage0") ) { - return false; - } - else { - TODO("Unknown fragment in cfg - " << p.cur().to_string()); - throw ::std::runtime_error(""); - } - } - }; - - Parser p { cfg.data() + 4 }; - success = H::check_cfg(p); - if( !p.consume_if(")") ) - throw ::std::runtime_error(format("Expected ')' after cfg condition - got", p.cur().to_string())); + success = Cfg_Check(cfg.c_str()); } else { // It's a target name @@ -412,25 +241,34 @@ PackageManifest PackageManifest::load_from_toml(const ::std::string& path) // If so, parse as if the path was `real_section....` if( success ) { - if( real_section == "dependencies" ) + if( real_section == "dependencies" + || real_section == "dev-dependencies" + || real_section == "build-dependencies" + ) { + ::std::vector<PackageRef>& dep_list = + real_section == "dependencies" ? rv.m_dependencies : + real_section == "build-dependencies" ? rv.m_build_dependencies : + /*real_section == "dev-dependencies" ? */ rv.m_dev_dependencies /*: + throw ""*/ + ; assert(key_val.path.size() > 3); const auto& depname = key_val.path[3]; // Find/create dependency descriptor - auto it = ::std::find_if(rv.m_dependencies.begin(), rv.m_dependencies.end(), [&](const auto& x) { return x.m_name == depname; }); - bool was_added = (it == rv.m_dependencies.end()); + auto it = ::std::find_if(dep_list.begin(), dep_list.end(), [&](const auto& x) { return x.m_name == depname; }); + bool was_added = (it == dep_list.end()); if( was_added ) { - it = rv.m_dependencies.insert(it, PackageRef{ depname }); + it = dep_list.insert(it, PackageRef{ depname }); } it->fill_from_kv(was_added, key_val, 4); } else { - TODO("Unknown manifest section for target - " << real_section); + TODO(toml_file.lexer() << ": Unknown manifest section '" << real_section << "' in `target`"); } } } @@ -514,10 +352,21 @@ PackageManifest PackageManifest::load_from_toml(const ::std::string& path) tgt.m_path = "src/main.rs"; } else { - // TODO: What about src/bin/foo/main.rs? + // TODO: Error if both exist + // TODO: More complex search rules tgt.m_path = ::helpers::path("src") / "bin" / tgt.m_name.c_str() + ".rs"; + if( !::std::ifstream(package_dir / tgt.m_path).good() ) + tgt.m_path = ::helpers::path("src") / "bin" / tgt.m_name.c_str() / "main.rs"; + //if( !::std::ifstream(package_dir / tgt.m_path).good() ) + // throw ::std::runtime_error(format("Unable to find source file for ", tgt.m_name, " - ", package_dir / tgt.m_path)); } break; + case PackageTarget::Type::Test: + case PackageTarget::Type::Bench: + // no defaults + break; + case PackageTarget::Type::Example: + TODO("Default/implicit path for examples"); } } if(tgt.m_name == "") @@ -528,6 +377,19 @@ PackageManifest PackageManifest::load_from_toml(const ::std::string& path) } } + // If there's a lib target, add a test target using the same path + { + auto it = ::std::find_if(rv.m_targets.begin(), rv.m_targets.end(), [&](const auto& t) { return t.m_type == PackageTarget::Type::Lib; }); + if( it != rv.m_targets.end() ) + { + auto path = it->m_path; + auto name = it->m_name + "-test"; + rv.m_targets.push_back(PackageTarget { PackageTarget::Type::Test }); + rv.m_targets.back().m_name = name; + rv.m_targets.back().m_path = path; + } + } + for(const auto& dep : rv.m_dependencies) { if( dep.m_optional ) @@ -542,7 +404,7 @@ PackageManifest PackageManifest::load_from_toml(const ::std::string& path) // Explicitly disabled `[package] build = false` rv.m_build_script = ""; } - else if( rv.m_build_script != "" ) + else if( rv.m_build_script == "" ) { // Not set, check for a "build.rs" file if( ::std::ifstream( package_dir / "build.rs").good() ) @@ -611,7 +473,26 @@ namespace } else if( key == "crate-type" ) { - // TODO: Support crate types + //assert_kv_size(kv, base_idx + 1); + //assert_type(kv, base_idx + 1); + assert(kv.path.size() == base_idx + 1); + if( !target.m_crate_types.empty() ) { + // TODO: Error, multiple instances + } + for(const auto& sv : kv.value.m_sub_values) + { + const auto& s = sv.as_string(); + if(s == "rlib") { + target.m_crate_types.push_back(PackageTarget::CrateType::rlib); + } + else if(s == "dylib") { + target.m_crate_types.push_back(PackageTarget::CrateType::dylib); + } + // TODO: Other crate types + else { + throw ::std::runtime_error(format("Unknown crate type - ", s)); + } + } } else if( key == "required-features" ) { @@ -798,12 +679,19 @@ void PackageManifest::set_features(const ::std::vector<::std::string>& features, it2->m_optional_enabled = true; } } + { + auto it2 = ::std::find_if(m_dev_dependencies.begin(), m_dev_dependencies.end(), [&](const auto& x){ return x.m_name == featname; }); + if(it2 != m_dev_dependencies.end()) + { + it2->m_optional_enabled = true; + } + } } // Return true if any features were activated //return start < m_active_features.size(); } -void PackageManifest::load_dependencies(Repository& repo, bool include_build) +void PackageManifest::load_dependencies(Repository& repo, bool include_build, bool include_dev) { TRACE_FUNCTION_F(m_name); DEBUG("Loading depencencies for " << m_name); @@ -819,16 +707,30 @@ void PackageManifest::load_dependencies(Repository& repo, bool include_build) dep.load_manifest(repo, base_path, include_build); } - // TODO: Only enable if build script overrides aren't enabled. + // Load build deps if there's a build script AND build scripts are enabled if( m_build_script != "" && include_build ) { + DEBUG("- Build dependencies"); for(auto& dep : m_build_dependencies) { if( dep.m_optional && !dep.m_optional_enabled ) { continue ; } - dep.load_manifest(repo, base_path, true); + dep.load_manifest(repo, base_path, include_build); + } + } + // Load dev dependencies if the caller has indicated they should be + if( include_dev ) + { + DEBUG("- Dev dependencies"); + for(auto& dep : m_dev_dependencies) + { + if( dep.m_optional && !dep.m_optional_enabled ) + { + continue ; + } + dep.load_manifest(repo, base_path, include_build); } } } @@ -1042,6 +944,10 @@ PackageVersionSpec PackageVersionSpec::from_string(const ::std::string& s) // Default, compatible pos ++; break; + case '~': + ty = PackageVersionSpec::Bound::Type::MinorCompatible; + pos ++; + break; case '=': ty = PackageVersionSpec::Bound::Type::Equal; pos ++; @@ -1090,6 +996,17 @@ PackageVersionSpec PackageVersionSpec::from_string(const ::std::string& s) { pos ++; v.patch = H::parse_i(s, pos); + + if( pos < s.size() && s[pos] == '-' ) + { + // Save tag (sequence of dot-seprated alpha-numeric identifiers) + auto tag_start = pos+1; + do { + // Could check the format, but meh. + pos ++; + } while(pos < s.size() && !isblank(s[pos]) && s[pos] != ',' ); + //v.tag = ::std::string(s.c_str() + tag_start, s.c_str() + pos); + } } else { @@ -1098,6 +1015,9 @@ PackageVersionSpec PackageVersionSpec::from_string(const ::std::string& s) } else { + // NOTE: This changes the behaviour of ~ rules to be bounded on the major version instead + if( ty == PackageVersionSpec::Bound::Type::MinorCompatible ) + ty = PackageVersionSpec::Bound::Type::Compatible; v.minor = 0; v.patch = 0; } @@ -1110,7 +1030,7 @@ PackageVersionSpec PackageVersionSpec::from_string(const ::std::string& s) break ; } while(pos < s.size() && s[pos++] == ','); if( pos != s.size() ) - throw ::std::runtime_error(::format( "Bad version string, pos=", pos )); + throw ::std::runtime_error(::format( "Bad version string '", s, "', pos=", pos )); return rv; } bool PackageVersionSpec::accepts(const PackageVersion& v) const @@ -1120,13 +1040,19 @@ bool PackageVersionSpec::accepts(const PackageVersion& v) const switch(b.ty) { case Bound::Type::Compatible: - // To be compatible, it has to be higher? - // - TODO: Isn't a patch version compatible? + // ^ rules are >= specified, and < next major/breaking if( !(v >= b.ver) ) return false; if( !(v < b.ver.next_breaking()) ) return false; break; + case Bound::Type::MinorCompatible: + // ~ rules are >= specified, and < next minor + if( !(v >= b.ver) ) + return false; + if( !(v < b.ver.next_minor()) ) + return false; + break; case Bound::Type::GreaterEqual: if( !(v >= b.ver) ) return false; diff --git a/tools/minicargo/manifest.h b/tools/minicargo/manifest.h index 4bb8b843..d0c537e5 100644 --- a/tools/minicargo/manifest.h +++ b/tools/minicargo/manifest.h @@ -26,6 +26,14 @@ struct PackageVersion static PackageVersion from_string(const ::std::string& s); + PackageVersion next_minor() const { + if(major == 0) { + return PackageVersion { 0, minor, patch+1 }; + } + else { + return PackageVersion { major, minor+1, 0 }; + } + } PackageVersion next_breaking() const { if(major == 0) { return PackageVersion { 0, minor + 1, 0 }; @@ -87,7 +95,8 @@ struct PackageVersionSpec { enum class Type { - Compatible, + Compatible, // "^" - Allows anything up to the next major version + MinorCompatible, // "~X.Y" - Allows anything up to the next minor version Greater, GreaterEqual, Equal, @@ -116,6 +125,7 @@ struct PackageVersionSpec switch(b.ty) { case Bound::Type::Compatible: os << "^"; break; + case Bound::Type::MinorCompatible: os << "~"; break; case Bound::Type::Greater: os << ">"; break; case Bound::Type::GreaterEqual: os << ">="; break; case Bound::Type::Equal: os << "="; break; @@ -180,6 +190,14 @@ struct PackageTarget Bench, Example, }; + enum class CrateType + { + dylib, + rlib, + staticlib, + cdylib, + proc_macro, + }; Type m_type; ::std::string m_name; @@ -192,6 +210,7 @@ struct PackageTarget bool m_is_proc_macro = false; bool m_is_own_harness = false; + ::std::vector<CrateType> m_crate_types; ::std::vector<::std::string> m_required_features; PackageTarget(Type ty): @@ -245,6 +264,7 @@ class PackageManifest ::std::vector<PackageRef> m_dependencies; ::std::vector<PackageRef> m_build_dependencies; + ::std::vector<PackageRef> m_dev_dependencies; ::std::vector<PackageTarget> m_targets; @@ -263,15 +283,18 @@ public: bool has_library() const; const PackageTarget& get_library() const; - bool foreach_binaries(::std::function<bool(const PackageTarget&)> cb) const { + bool foreach_ty(PackageTarget::Type ty, ::std::function<bool(const PackageTarget&)> cb) const { for(const auto& t : m_targets ) { - if( t.m_type == PackageTarget::Type::Bin ) { + if( t.m_type == ty ) { if( !cb(t) ) return false; } } return true; } + bool foreach_binaries(::std::function<bool(const PackageTarget&)> cb) const { + return foreach_ty(PackageTarget::Type::Bin, cb); + } const ::helpers::path directory() const { return ::helpers::path(m_manifest_path).parent(); @@ -294,12 +317,15 @@ public: const ::std::vector<PackageRef>& build_dependencies() const { return m_build_dependencies; } + const ::std::vector<PackageRef>& dev_dependencies() const { + return m_dev_dependencies; + } const ::std::vector<::std::string>& active_features() const { return m_active_features; } void set_features(const ::std::vector<::std::string>& features, bool enable_default); - void load_dependencies(Repository& repo, bool include_build); + void load_dependencies(Repository& repo, bool include_build, bool include_dev=false); void load_build_script(const ::std::string& path); }; diff --git a/tools/minicargo/repository.cpp b/tools/minicargo/repository.cpp index f5ff5ea8..060c5207 100644 --- a/tools/minicargo/repository.cpp +++ b/tools/minicargo/repository.cpp @@ -75,7 +75,7 @@ void Repository::load_vendored(const ::helpers::path& path) } } - //DEBUG("Package '" << name << "' v" << ver); + DEBUG("Vendored package '" << name << "' v" << ver); if(name == "") continue ; @@ -91,6 +91,7 @@ void Repository::load_vendored(const ::helpers::path& path) } while( FindNextFile(find_handle, &find_data) ); FindClose(find_handle); #endif + DEBUG("Loaded " << m_cache.size() << " vendored packages"); } ::std::shared_ptr<PackageManifest> Repository::from_path(::helpers::path in_path) diff --git a/tools/minicargo/stringlist.h b/tools/minicargo/stringlist.h index 4381121b..08b74c4b 100644 --- a/tools/minicargo/stringlist.h +++ b/tools/minicargo/stringlist.h @@ -78,7 +78,7 @@ public: }; class StringListKV: private StringList { - ::std::vector<const char*> m_keys; + StringList m_keys; public: StringListKV() { @@ -99,6 +99,16 @@ public: m_keys.push_back(k); StringList::push_back(v); } + void push_back(::std::string k, ::std::string v) + { + m_keys.push_back(k); + StringList::push_back(v); + } + void push_back(::std::string k, const char* v) + { + m_keys.push_back(k); + StringList::push_back(v); + } struct Iter { const StringListKV& v; @@ -108,7 +118,7 @@ public: this->i++; } ::std::pair<const char*,const char*> operator*() { - return ::std::make_pair(this->v.m_keys[this->i], this->v.get_vec()[this->i]); + return ::std::make_pair(this->v.m_keys.get_vec()[this->i], this->v.get_vec()[this->i]); } bool operator!=(const Iter& x) const { return this->i != x.i; @@ -118,7 +128,7 @@ public: return Iter { *this, 0 }; } Iter end() const { - return Iter { *this, m_keys.size() }; + return Iter { *this, m_keys.get_vec().size() }; } friend ::std::ostream& operator<<(::std::ostream& os, const StringListKV& x) { diff --git a/tools/standalone_miri/Makefile b/tools/standalone_miri/Makefile index f4dc0d0d..0a8bd672 100644 --- a/tools/standalone_miri/Makefile +++ b/tools/standalone_miri/Makefile @@ -11,7 +11,7 @@ V ?= @ OBJDIR := .obj/ BIN := ../bin/standalone_miri$(EXESUF) -OBJS := main.o debug.o mir.o lex.o value.o module_tree.o hir_sim.o miri.o +OBJS := main.o debug.o mir.o lex.o value.o module_tree.o hir_sim.o miri.o rc_string.o LINKFLAGS := -g -lpthread CXXFLAGS := -Wall -std=c++14 -g -O2 @@ -37,6 +37,11 @@ $(OBJDIR)%.o: %.cpp @echo [CXX] $< $V$(CXX) -o $@ -c $< $(CXXFLAGS) -MMD -MP -MF $@.dep +$(OBJDIR)%.o: ../../src/%.cpp + @mkdir -p $(dir $@) + @echo [CXX] $< + $V$(CXX) -o $@ -c $< $(CXXFLAGS) -MMD -MP -MF $@.dep + ../bin/common_lib.a: make -C ../common diff --git a/tools/standalone_miri/debug.cpp b/tools/standalone_miri/debug.cpp index c49df960..534790cd 100644 --- a/tools/standalone_miri/debug.cpp +++ b/tools/standalone_miri/debug.cpp @@ -11,14 +11,20 @@ unsigned DebugSink::s_indent = 0; ::std::unique_ptr<std::ofstream> DebugSink::s_out_file; -DebugSink::DebugSink(::std::ostream& inner): - m_inner(inner) +DebugSink::DebugSink(::std::ostream& inner, bool stderr_too): + m_inner(inner), + m_stderr_too(stderr_too) { } DebugSink::~DebugSink() { m_inner << "\n"; m_inner.flush(); + m_inner.flags({}); + if( m_stderr_too ) + { + ::std::cerr << ::std::endl; + } } void DebugSink::set_output_file(const ::std::string& s) { @@ -30,6 +36,7 @@ bool DebugSink::enabled(const char* fcn_name) } DebugSink DebugSink::get(const char* fcn_name, const char* file, unsigned line, DebugLevel lvl) { + bool stderr_too = false; auto& sink = s_out_file ? *s_out_file : ::std::cout; for(size_t i = s_indent; i--;) sink << " "; @@ -49,15 +56,18 @@ DebugSink DebugSink::get(const char* fcn_name, const char* file, unsigned line, break; case DebugLevel::Error: sink << "ERROR: "; + stderr_too = true; break; case DebugLevel::Fatal: sink << "FATAL: "; + stderr_too = true; break; case DebugLevel::Bug: sink << "BUG: " << file << ":" << line << ": "; + stderr_too = true; break; } - return DebugSink(sink); + return DebugSink(sink, stderr_too); } void DebugSink::inc_indent() { diff --git a/tools/standalone_miri/debug.hpp b/tools/standalone_miri/debug.hpp index b3b0d76f..9de6231b 100644 --- a/tools/standalone_miri/debug.hpp +++ b/tools/standalone_miri/debug.hpp @@ -21,17 +21,26 @@ enum class DebugLevel { Bug, }; -class DebugSink +class DebugSink//: + //public ::std::ostream { static unsigned s_indent; static ::std::unique_ptr<std::ofstream> s_out_file; ::std::ostream& m_inner; - DebugSink(::std::ostream& inner); + bool m_stderr_too; + DebugSink(::std::ostream& inner, bool stderr_too); public: ~DebugSink(); template<typename T> - ::std::ostream& operator<<(const T& v) { return m_inner << v; } + DebugSink& operator<<(const T& v) { + if( m_stderr_too ) + { + ::std::cerr << v; + } + m_inner << v; + return *this; + } static void set_output_file(const ::std::string& s); static bool enabled(const char* fcn_name); @@ -102,4 +111,6 @@ struct DebugExceptionError: #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; throw DebugExceptionTodo{}; } while(0) #define LOG_BUG(strm) do { DebugSink::get(__FUNCTION__,__FILE__,__LINE__,DebugLevel::Bug) << "BUG: " << strm; abort(); } while(0) -#define LOG_ASSERT(cnd,strm) do { if( !(cnd) ) { LOG_BUG("Assertion failure: " #cnd " - " << strm); } } while(0) +#define LOG_ASSERT(cnd,strm) do { if( !(cnd) ) { LOG_ERROR("Assertion failure: " #cnd " - " << strm); } } while(0) + +#define FMT_STRING(...) (dynamic_cast<::std::stringstream&>(::std::stringstream() << __VA_ARGS__).str()) diff --git a/tools/standalone_miri/ffi.cpp b/tools/standalone_miri/ffi.cpp new file mode 100644 index 00000000..827d862f --- /dev/null +++ b/tools/standalone_miri/ffi.cpp @@ -0,0 +1,141 @@ +/* + * mrustc Standalone MIRI + * - by John Hodge (Mutabah) + * + * ffi.cpp + * - FFI wrappers + */ + +/// Argument reference (for checking validity) +struct ArgRef +{ + uint8_t idx; // if 255, it's not referencing anything + + static ArgRef null() { return ArgRef { 255 }; } +}; + +/// Representation of a FFI type (defined by the .api file) +/// - These specify various flags used to tag pointers in the MIR +struct FfiType +{ + // Pointer: + // - Mutability + // - Nullability + // - Array size (number of allocated elements) + // > Can be either a number, or an argument name + // - Valid size (number of initialised elements) + // - Allocation source + struct Pointer { + bool is_mut; + bool is_nullable; + ArgOrCount alloc_size; + ArgOrCount valid_size; + ArgRef alloc_source; // Can be "null" + }; + ::std::vector<Pointer> pointers; // Reverse list (last entry is the outermost pointer) + + // Datatypes: + // - `void` + // - size/alignment + // - u8,u16,... + // - function + // - Name + enum class Datatype { + Void, + Signed, + Unsigned, + Float, + Function, + } datatype; + union Meta { + struct { + size_t size; + size_t align; + ::std::string tag; + } void_data; + unsigned bits; + struct { + ArgRef name_source; + } function; + } meta; +}; + + +struct FfiShim +{ + class ValueRef + { + public: + static ValueRef new_global(std::string name); + static ValueRef new_local(std::string name); + static ValueRef new_deref(ValueRef target); + }; + class Expr + { + enum { + LITERAL, + VALUE, + CALL, + } ty; + union { + } data; + public: + static Expr new_lit(uint64_t v); + static Expr new_value(ValueRef vr); + static Expr new_call_int(::std::vector<::std::string> path, ::std::vector<Expr> args); + }; + struct Stmt; + struct Block + { + ::std::vector<Stmt> statements; + Expr val; + }; + class Stmt + { + enum { + DEFINE, // `let foo = bar;` + ASSIGN, // `foo ?= bar;` + IF, + } ty; + union { + struct { + ::std::string slot; + Expr value; + } define; + struct { + ValueRef slot; + Expr value; + } assign; + struct { + Expr cond; + Block true_arm; + Block false_arm; + } if_block; + }; + }; +}; + +struct FfiFunction +{ + ::std::vector<FfiType> arg_types; + FfiType ret_type; + ::std::vector<std::string> arg_names; + + // Either directly defers to a function + ::std::string library; + ::std::string function; + + // Or, it has code for more advanced checking + //FfiShimExpr code; + + bool call(Value& rv, ::std::vector<Value> args) const; +}; + +bool FfiFunction::call(Value& rv, ::std::vector<Value> args) const +{ + +} + +bool call_ffi(Value& rv, const ::std::string& link_name, const ::std::string& abi, ::std::vector<Value> args) +{ +} diff --git a/tools/standalone_miri/hir_sim.cpp b/tools/standalone_miri/hir_sim.cpp index 88739730..9d497054 100644 --- a/tools/standalone_miri/hir_sim.cpp +++ b/tools/standalone_miri/hir_sim.cpp @@ -42,18 +42,18 @@ size_t HIR::TypeRef::get_size(size_t ofs) const // Need to look up the metadata type for the actual type if( this->inner_type == RawType::Composite ) { - if( this->composite_type->dst_meta == RawType::Unreachable ) + if( this->composite_type().dst_meta == RawType::Unreachable ) { return POINTER_SIZE; } // Special case: extern types (which appear when a type is only ever used by pointer) - if( this->composite_type->dst_meta == RawType::Unit ) + if( this->composite_type().dst_meta == RawType::Unit ) { return POINTER_SIZE; } // TODO: Ideally, this inner type wouldn't be unsized itself... but checking that would be interesting. - return POINTER_SIZE + this->composite_type->dst_meta.get_size(); + return POINTER_SIZE + this->composite_type().dst_meta.get_size(); } else if( this->inner_type == RawType::Str ) return POINTER_SIZE*2; @@ -77,7 +77,8 @@ size_t HIR::TypeRef::get_size(size_t ofs) const return 0; case RawType::Composite: // NOTE: Don't care if the type has metadata - return this->composite_type->size; + LOG_ASSERT(this->composite_type().populated, "Getting size of non-defined type - " << *this); + return this->composite_type().size; case RawType::Unreachable: LOG_BUG("Attempting to get size of an unreachable type, " << *this); case RawType::TraitObject: @@ -111,6 +112,56 @@ size_t HIR::TypeRef::get_size(size_t ofs) const throw ""; } } +size_t HIR::TypeRef::get_align(size_t ofs) const +{ + if( const auto* w = this->get_wrapper(ofs) ) + { + LOG_TODO("get_align " << *this); + } + else + { + switch(this->inner_type) + { + case RawType::Unit: + return 1; + case RawType::Composite: + // NOTE: Don't care if the type has metadata + LOG_ASSERT(this->composite_type().populated, "Getting alignment of non-defined type - " << *this); + return this->composite_type().alignment; + case RawType::TraitObject: + case RawType::Str: + return 1; + case RawType::U8: case RawType::I8: + return 1; + case RawType::U16: case RawType::I16: + return 2; + case RawType::U32: case RawType::I32: + return 4; + case RawType::U64: case RawType::I64: + return 8; + case RawType::U128: case RawType::I128: + return 16; + + case RawType::Bool: + return 1; + case RawType::Char: + return 4; + + case RawType::F32: + return 4; + case RawType::F64: + return 8; + + case RawType::Function: // This should probably be invalid? + case RawType::USize: + case RawType::ISize: + return POINTER_SIZE; + case RawType::Unreachable: + LOG_BUG("Getting alignment of unreachable type"); + } + throw ""; + } +} bool HIR::TypeRef::has_slice_meta(size_t& out_inner_size) const { if( const auto* w = this->get_wrapper() ) @@ -168,7 +219,7 @@ bool HIR::TypeRef::has_pointer() const if( this->inner_type == RawType::Composite ) { // Still not sure, check the inner for any pointers. - for(const auto& fld : this->composite_type->fields) + for(const auto& fld : this->composite_type().fields) { if( fld.second.has_pointer() ) return true; @@ -196,13 +247,13 @@ const HIR::TypeRef* HIR::TypeRef::get_unsized_type(size_t& running_inner_size) c switch(this->inner_type) { case RawType::Composite: - if(!this->composite_type->variants.empty()) + if(!this->composite_type().variants.empty()) return nullptr; - if(this->composite_type->fields.empty()) + if(this->composite_type().fields.empty()) return nullptr; - running_inner_size = this->composite_type->fields.back().first; + running_inner_size = this->composite_type().fields.back().first; size_t tmp; - return this->composite_type->fields.back().second.get_unsized_type(tmp); + return this->composite_type().fields.back().second.get_unsized_type(tmp); case RawType::TraitObject: case RawType::Str: return this; @@ -229,11 +280,12 @@ HIR::TypeRef HIR::TypeRef::get_meta_type() const switch(this->inner_type) { case RawType::Composite: - if( this->composite_type->dst_meta == RawType::Unreachable ) + if( this->composite_type().dst_meta == RawType::Unreachable ) return TypeRef(RawType::Unreachable); - return this->composite_type->dst_meta; + return this->composite_type().dst_meta; case RawType::TraitObject: - return ::HIR::TypeRef(this->composite_type).wrap( TypeWrapper::Ty::Pointer, static_cast<size_t>(BorrowType::Shared) ); + LOG_ASSERT(this->ptr.composite_type, "get_meta_type - " << *this); + return ::HIR::TypeRef(this->ptr.composite_type).wrap( TypeWrapper::Ty::Pointer, static_cast<size_t>(BorrowType::Shared) ); case RawType::Str: return TypeRef(RawType::USize); default: @@ -249,7 +301,7 @@ HIR::TypeRef HIR::TypeRef::get_field(size_t idx, size_t& ofs) const if( w->type == TypeWrapper::Ty::Slice ) { // TODO - throw "TODO"; + LOG_TODO("Field on slice - " << *this << " #" << idx); } else if( w->type == TypeWrapper::Ty::Array ) { @@ -260,21 +312,20 @@ HIR::TypeRef HIR::TypeRef::get_field(size_t idx, size_t& ofs) const } else { - throw "ERROR"; + LOG_ERROR("Field on unknown wrapper type - " << *this << " #" << idx); } } else { if( this->inner_type == RawType::Composite ) { - LOG_ASSERT(idx < this->composite_type->fields.size(), "Field " << idx << " out of bounds in type " << *this); - ofs = this->composite_type->fields.at(idx).first; - return this->composite_type->fields.at(idx).second; + LOG_ASSERT(idx < this->composite_type().fields.size(), "Field " << idx << " out of bounds in type " << *this); + ofs = this->composite_type().fields.at(idx).first; + return this->composite_type().fields.at(idx).second; } else { - ::std::cerr << *this << " doesn't have fields" << ::std::endl; - throw "ERROR"; + LOG_ERROR(*this << " doesn't have fields"); } } } @@ -282,14 +333,14 @@ size_t HIR::TypeRef::get_field_ofs(size_t base_idx, const ::std::vector<size_t>& { 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; + 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; + ofs += ty_p->composite_type().fields.at(idx).first; + ty_p = &ty_p->composite_type().fields.at(idx).second; } ty = *ty_p; return ofs; @@ -346,19 +397,30 @@ namespace HIR { os << "()"; break; case RawType::Composite: - os << x.composite_type->my_path; + os << x.composite_type().my_path; //os << "composite_" << x.composite_type; break; case RawType::Unreachable: os << "!"; break; - case RawType::Function: - os << "function_?"; - break; + case RawType::Function: { + assert( x.ptr.function_type ); + const auto& ft = *x.ptr.function_type; + if( ft.unsafe ) + os << "unsafe "; + if( ft.abi != "Rust" ) + os << "extern \"" << ft.abi << "\" "; + os << "fn( "; + for(const auto& a : ft.args) + os << a << ", "; + os << ")"; + if( ft.ret != RawType::Unit ) + os << "-> " << ft.ret; + } break; case RawType::TraitObject: os << "dyn "; - if( x.composite_type ) - os << x.composite_type->my_path; + if( x.ptr.composite_type ) + os << x.composite_type().my_path; else os << "?"; break; diff --git a/tools/standalone_miri/hir_sim.hpp b/tools/standalone_miri/hir_sim.hpp index 62248fe9..81d3635a 100644 --- a/tools/standalone_miri/hir_sim.hpp +++ b/tools/standalone_miri/hir_sim.hpp @@ -9,20 +9,26 @@ #include <string> #include <vector> #include <memory> +#include "../../src/include/rc_string.hpp" const size_t POINTER_SIZE = 8; +#define __ORD(fld) do { auto o = ::ord(this->fld, x.fld); if( o != OrdEqual ) return o; } while(0) +#define __ORD_C(ty, fld) do { auto o = ::ord((ty)this->fld, (ty)x.fld); if( o != OrdEqual ) return o; } while(0) #define __NE(fld) if(this->fld != x.fld) return true #define __LT(fld) if(this->fld != x.fld) return this->fld < x.fld +#if 0 enum Ordering { OrdLess, OrdEqual, OrdGreater, }; +#endif struct DataType; +struct FunctionType; enum class RawType { @@ -56,22 +62,22 @@ struct TypeWrapper } type; size_t size; + Ordering ord(const TypeWrapper& x) const { + __ORD_C(int, type); + __ORD(size); + return OrdEqual; + } bool operator==(const TypeWrapper& x) const { - return !(*this != x); + return this->ord(x) == OrdEqual; } bool operator!=(const TypeWrapper& x) const { - __NE(type); - __NE(size); - return false; + return this->ord(x) != OrdEqual; } bool operator<(const TypeWrapper& x) const { - __LT(type); - __LT(size); - return false; + return this->ord(x) == OrdLess; } }; - namespace HIR { enum class BorrowType @@ -91,24 +97,35 @@ namespace HIR { // Top to bottom list of wrappers (first entry is the outermost wrapper) ::std::vector<TypeWrapper> wrappers; RawType inner_type = RawType::Unit; - const DataType* composite_type = nullptr; + union { + const DataType* composite_type; + const FunctionType* function_type; + } ptr; TypeRef() { + ptr.composite_type = nullptr; } explicit TypeRef(const DataType* dt): - inner_type(RawType::Composite), - composite_type(dt) + inner_type(RawType::Composite) + { + ptr.composite_type = dt; + } + explicit TypeRef(const FunctionType* fp): + inner_type(RawType::Function) { + ptr.function_type = fp; } explicit TypeRef(RawType rt): inner_type(rt) { + ptr.composite_type = nullptr; } explicit TypeRef(CoreType ct): inner_type(ct.raw_type) { + ptr.composite_type = nullptr; } static TypeRef diverge() { TypeRef rv; @@ -122,6 +139,7 @@ namespace HIR { } size_t get_size(size_t ofs=0) const; + size_t get_align(size_t ofs=0) const; // Returns true if this (unsized) type is a wrapper around a slice // - Fills `out_inner_size` with the size of the slice element @@ -158,6 +176,17 @@ namespace HIR { // Get the offset and type of a field (recursing using `other_idx`) size_t get_field_ofs(size_t idx, const ::std::vector<size_t>& other_idx, TypeRef& ty) const; + const DataType& composite_type() const { + assert(inner_type == RawType::Composite || inner_type == RawType::TraitObject); + assert(ptr.composite_type); + return *ptr.composite_type; + } + const FunctionType& function_type() const { + assert(inner_type == RawType::Function); + assert(ptr.function_type); + return *ptr.function_type; + } + bool operator==(const RawType& x) const { if( this->wrappers.size() != 0 ) return false; @@ -166,20 +195,20 @@ namespace HIR { bool operator!=(const RawType& x) const { return !(*this == x); } + Ordering ord(const TypeRef& x) const { + __ORD(wrappers); + __ORD_C(int, inner_type); + __ORD_C(uintptr_t, ptr.composite_type); // pointer comparison only + return OrdEqual; + } bool operator==(const TypeRef& x) const { - return !(*this != x); + return this->ord(x) == OrdEqual; } bool operator!=(const TypeRef& x) const { - __NE(wrappers); - __NE(inner_type); - __NE(composite_type); - return false; + return this->ord(x) != OrdEqual; } bool operator<(const TypeRef& x) const { - __LT(wrappers); - __LT(inner_type); - __LT(composite_type); - return false; + return this->ord(x) == OrdLess; } friend ::std::ostream& operator<<(::std::ostream& os, const TypeRef& x); @@ -189,18 +218,19 @@ namespace HIR { { ::std::string crate_name; ::std::vector<::std::string> ents; + Ordering ord(const SimplePath& x) const { + __ORD(crate_name); + __ORD(ents); + return OrdEqual; + } bool operator==(const SimplePath& x) const { - return !(*this != x); + return this->ord(x) == OrdEqual; } bool operator!=(const SimplePath& x) const { - __NE(crate_name); - __NE(ents); - return false; + return this->ord(x) != OrdEqual; } bool operator<(const SimplePath& x) const { - __LT(crate_name); - __LT(ents); - return false; + return this->ord(x) == OrdLess; } friend ::std::ostream& operator<<(::std::ostream& os, const SimplePath& x); }; @@ -221,18 +251,19 @@ namespace HIR { m_simplepath(sp) { } + Ordering ord(const GenericPath& x) const { + __ORD(m_simplepath); + __ORD(m_params.tys); + return OrdEqual; + } bool operator==(const GenericPath& x) const { - return !(*this != x); + return this->ord(x) == OrdEqual; } bool operator!=(const GenericPath& x) const { - __NE(m_simplepath); - __NE(m_params.tys); - return false; + return this->ord(x) != OrdEqual; } bool operator<(const GenericPath& x) const { - __LT(m_simplepath); - __LT(m_params.tys); - return false; + return this->ord(x) == OrdLess; } friend ::std::ostream& operator<<(::std::ostream& os, const GenericPath& x); @@ -263,22 +294,21 @@ namespace HIR { { } + Ordering ord(const Path& x) const { + __ORD(m_type); + __ORD(m_trait); + __ORD(m_name); + __ORD(m_params.tys); + return OrdEqual; + } bool operator==(const Path& x) const { - return !(*this != x); + return this->ord(x) == OrdEqual; } bool operator!=(const Path& x) const { - __NE(m_type); - __NE(m_trait); - __NE(m_name); - __NE(m_params.tys); - return false; + return this->ord(x) != OrdEqual; } bool operator<(const Path& x) const { - __LT(m_type); - __LT(m_trait); - __LT(m_name); - __LT(m_params.tys); - return false; + return this->ord(x) == OrdLess; } friend ::std::ostream& operator<<(::std::ostream& os, const Path& x); diff --git a/tools/standalone_miri/lex.cpp b/tools/standalone_miri/lex.cpp index 07427bde..79e94224 100644 --- a/tools/standalone_miri/lex.cpp +++ b/tools/standalone_miri/lex.cpp @@ -7,6 +7,8 @@ */ #include "lex.hpp" #include <cctype> +#include <sstream> +#include "debug.hpp" #include <iostream> bool Token::operator==(TokenClass tc) const @@ -25,7 +27,7 @@ bool Token::operator==(const char* s) const uint64_t Token::integer() const { if( this->type != TokenClass::Integer ) - throw ""; + throw ::std::runtime_error(FMT_STRING("Expected interger, got " << *this)); return this->numbers.int_val; } double Token::real() const diff --git a/tools/standalone_miri/linux.api b/tools/standalone_miri/linux.api new file mode 100644 index 00000000..a4b8dd36 --- /dev/null +++ b/tools/standalone_miri/linux.api @@ -0,0 +1,50 @@ +# +# Expression grammar: +# `let <name> = <expr>;` +# `<slot> <op>= <expr>;` +# `<expr>` + +# `signal` - Just ignore it +fn signal(..) -> *const [null] void [size(0)] { + 0 +} +fn memchr(ptr: *const [count(n)] u8, c: u8, n: usize) -> *const [null,alloc(ptr)] u8 = "":"memchr"; +fn memrchr(ptr: *const [count(n)] u8, c: u8, n: usize) -> *const [null,alloc(ptr)] u8 = "":"memrchr"; +fn strlen(ptr: *const [cstr] u8) -> usize = "":"strlen"; + + +#fn write(fd: i32, count: isize, buf: *const void [size(count)]) -> i32 = "":"write"; +fn write(fd: i32, count: isize, buf: *const void) -> i32 { + miri::assert("invalid fd passed", fd > 0); + miri::ensure_valid_read("source buffer invalid", buf, 0, count); + miri::call_i32("", "write", fd, count, buf) +} +fn sysconf(name: i32) -> usize = "":"sysconf"; + + +# 64-bit linux pthread_attr_t +type pthread_attr_t = void [size(56),align(8)]; +fn pthread_attr_init(*mut pthread_attr_t) -> i32 = "":"pthread_attr_init"; +fn pthread_attr_destroy(*mut pthread_attr_t) -> i32 = "":"pthread_attr_destroy"; + +type pthread_key_t = u32; +static PTHREAD_NEXT_KEY: u32 = 1; +static PTHREAD_TLS: Map<u32,u64>; +fn pthread_key_create(ptr: *mut pthread_key_t) -> i32 { + let key = PTHREAD_NEXT_KEY; + PTHREAD_NEXT_KEY += 1; + *ptr = key; + 0 +} +fn pthread_key_delete(key: pthread_key_t) -> i32 { + let _ = Map::remove(key); + 0 +} +fn pthread_setspecific(key: pthread_key_t, val: u64) -> i32 { + Map::set(PTHREAD_TLS, key, val); + 0 +} +fn pthread_getspecific(key: pthread_key_t) -> u64 { + let rv_opt = Map::get(PTHREAD_TLS, key); + Option::unwrap_or(rv_opt, 0) +} diff --git a/tools/standalone_miri/main.cpp b/tools/standalone_miri/main.cpp index deed08be..8ee118f7 100644 --- a/tools/standalone_miri/main.cpp +++ b/tools/standalone_miri/main.cpp @@ -51,6 +51,7 @@ int main(int argc, const char* argv[]) try { tree.load_file(opts.infile); + tree.validate(); } catch(const DebugExceptionTodo& /*e*/) { @@ -71,26 +72,28 @@ int main(int argc, const char* argv[]) return 1; } + // Create argc/argv based on input arguments - auto argv_alloc = Allocation::new_alloc((1 + opts.args.size()) * POINTER_SIZE); - argv_alloc->write_usize(0 * POINTER_SIZE, 0); - argv_alloc->relocations.push_back({ 0 * POINTER_SIZE, RelocationPtr::new_ffi(FFIPointer { "", (void*)(opts.infile.c_str()), opts.infile.size() + 1 }) }); + auto argv_alloc = Allocation::new_alloc((1 + opts.args.size()) * POINTER_SIZE, "argv"); + argv_alloc->write_usize(0 * POINTER_SIZE, Allocation::PTR_BASE); + argv_alloc->relocations.push_back({ 0 * POINTER_SIZE, RelocationPtr::new_ffi(FFIPointer::new_const_bytes("argv0", opts.infile.c_str(), opts.infile.size() + 1)) }); for(size_t i = 0; i < opts.args.size(); i ++) { - argv_alloc->write_usize((1 + i) * POINTER_SIZE, 0); - argv_alloc->relocations.push_back({ (1 + i) * POINTER_SIZE, RelocationPtr::new_ffi({ "", (void*)(opts.args[0]), ::std::strlen(opts.args[0]) + 1 }) }); + argv_alloc->write_usize((1 + i) * POINTER_SIZE, Allocation::PTR_BASE); + argv_alloc->relocations.push_back({ (1 + i) * POINTER_SIZE, RelocationPtr::new_ffi(FFIPointer::new_const_bytes("argv", opts.args[i], ::std::strlen(opts.args[i]) + 1)) }); } - + LOG_DEBUG("argv_alloc = " << *argv_alloc); + // Construct argc/argv values auto val_argc = Value::new_isize(1 + opts.args.size()); auto argv_ty = ::HIR::TypeRef(RawType::I8).wrap(TypeWrapper::Ty::Pointer, 0 ).wrap(TypeWrapper::Ty::Pointer, 0); - auto val_argv = Value::new_pointer(argv_ty, 0, RelocationPtr::new_alloc(argv_alloc)); + auto val_argv = Value::new_pointer(argv_ty, Allocation::PTR_BASE, RelocationPtr::new_alloc(argv_alloc)); // Catch various exceptions from the interpreter try { InterpreterThread root_thread(tree); - + ::std::vector<Value> args; args.push_back(::std::move(val_argc)); args.push_back(::std::move(val_argv)); diff --git a/tools/standalone_miri/mir.cpp b/tools/standalone_miri/mir.cpp index a0601823..bc456ca6 100644 --- a/tools/standalone_miri/mir.cpp +++ b/tools/standalone_miri/mir.cpp @@ -5,10 +5,13 @@ * mir/mir.cpp * - MIR (Middle Intermediate Representation) definitions */ +#include "../../src/include/rc_string.hpp" #include "../../src/mir/mir.hpp" #include "hir_sim.hpp" #include <iostream> +#include <algorithm> // std::min +#if 0 namespace std { template <typename T> inline ::std::ostream& operator<<(::std::ostream& os, const ::std::vector<T>& v) { @@ -26,6 +29,7 @@ namespace std { return os; } } +#endif namespace MIR { ::std::ostream& operator<<(::std::ostream& os, const Constant& v) { @@ -62,44 +66,95 @@ namespace MIR { os << "\"" << e << "\""; ), (Const, - os << e.p; + os << *e.p; ), (ItemAddr, - os << "&" << e; + os << "&" << *e; ) ) return os; } - ::std::ostream& operator<<(::std::ostream& os, const LValue& x) + void LValue::RefCommon::fmt(::std::ostream& os) const { - TU_MATCHA( (x), (e), + TU_MATCHA( (m_lv->m_root), (e), (Return, - os << "Return"; + os << "retval"; ), (Argument, - os << "Argument(" << e.idx << ")"; + os << "a" << e; ), (Local, - os << "Local(" << e << ")"; + os << "_" << e; ), (Static, - os << "Static(" << e << ")"; - ), - (Field, - os << "Field(" << e.field_index << ", " << *e.val << ")"; - ), - (Deref, - os << "Deref(" << *e.val << ")"; - ), - (Index, - os << "Index(" << *e.val << ", " << *e.idx << ")"; - ), - (Downcast, - os << "Downcast(" << e.variant_index << ", " << *e.val << ")"; + os << "(" << e << ")"; ) ) + for(size_t i = 0; i < m_wrapper_count; i ++) + { + const LValue::Wrapper& w = m_lv->m_wrappers.at(i); + TU_MATCHA( (w), (e), + (Field, + os << "." << e; + ), + (Deref, + os << "*"; + ), + (Index, + os << "[_" << e << "]"; + ), + (Downcast, + os << "#" << e; + ) + ) + } + } + + ::std::ostream& operator<<(::std::ostream& os, const LValue& x) + { + LValue::CRef(x).fmt(os); return os; } + + Ordering LValue::Storage::ord(const LValue::Storage& x) const + { + if( x.is_Static() ) + { + if( this->is_Static() ) + return this->as_Static().ord( x.as_Static() ); + else + return OrdLess; + } + else + { + if( this->is_Static() ) + return OrdGreater; + } + + return ::ord(this->val, x.val); + } + Ordering LValue::ord(const LValue& x) const + { + auto rv = m_root.ord(x.m_root); + if( rv != OrdEqual ) + return rv; + return ::ord(m_wrappers, x.m_wrappers); + } + Ordering LValue::RefCommon::ord(const LValue::RefCommon& x) const + { + Ordering rv; + //TRACE_FUNCTION_FR(FMT_CB(ss, this->fmt(ss); ss << " ? "; x.fmt(ss);), rv); + rv = m_lv->m_root.ord(x.m_lv->m_root); + if( rv != OrdEqual ) + return rv; + for(size_t i = 0; i < ::std::min(m_wrapper_count, x.m_wrapper_count); i ++) + { + rv = m_lv->m_wrappers[i].ord(x.m_lv->m_wrappers[i]); + if( rv != OrdEqual ) + return rv; + } + return (rv = ::ord(m_wrapper_count, x.m_wrapper_count)); + } ::std::ostream& operator<<(::std::ostream& os, const Param& x) { TU_MATCHA( (x), (e), @@ -296,5 +351,10 @@ namespace MIR { ) return os; } + + EnumCachePtr::~EnumCachePtr() + { + assert(!this->p); + } } 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 { diff --git a/tools/standalone_miri/miri.hpp b/tools/standalone_miri/miri.hpp index 6f02ffee..f835fedb 100644 --- a/tools/standalone_miri/miri.hpp +++ b/tools/standalone_miri/miri.hpp @@ -13,10 +13,16 @@ struct ThreadState { static unsigned s_next_tls_key; unsigned call_stack_depth; - ::std::vector<uint64_t> tls_values; + ::std::vector< ::std::pair<uint64_t, RelocationPtr> > tls_values; + + unsigned panic_count; + bool panic_active; + Value panic_value; ThreadState(): call_stack_depth(0) + ,panic_count(0) + ,panic_active(false) { } @@ -35,8 +41,11 @@ class InterpreterThread friend struct MirHelpers; struct StackFrame { + static unsigned s_next_frame_index; + unsigned frame_index; + ::std::function<bool(Value&,Value)> cb; - const Function& fcn; + const Function* fcn; Value ret; ::std::vector<Value> args; ::std::vector<Value> locals; @@ -56,11 +65,13 @@ class InterpreterThread ModuleTree& m_modtree; ThreadState m_thread; + size_t m_instruction_count; ::std::vector<StackFrame> m_stack; public: InterpreterThread(ModuleTree& modtree): - m_modtree(modtree) + m_modtree(modtree), + m_instruction_count(0) { } ~InterpreterThread(); @@ -77,7 +88,7 @@ private: // Returns true if the call was resolved instantly bool call_extern(Value& ret_val, const ::std::string& name, const ::std::string& abi, ::std::vector<Value> args); // Returns true if the call was resolved instantly - bool call_intrinsic(Value& ret_val, const ::std::string& name, const ::HIR::PathParams& pp, ::std::vector<Value> args); + bool call_intrinsic(Value& ret_val, const RcString& name, const ::HIR::PathParams& pp, ::std::vector<Value> args); // Returns true if the call was resolved instantly bool drop_value(Value ptr, const ::HIR::TypeRef& ty, bool is_shallow=false); diff --git a/tools/standalone_miri/module_tree.cpp b/tools/standalone_miri/module_tree.cpp index 8e0a231a..91d82d85 100644 --- a/tools/standalone_miri/module_tree.cpp +++ b/tools/standalone_miri/module_tree.cpp @@ -57,6 +57,24 @@ void ModuleTree::load_file(const ::std::string& path) // Keep going! } } +void ModuleTree::validate() +{ + TRACE_FUNCTION_R("", ""); + for(const auto& dt : this->data_types) + { + //LOG_ASSERT(dt.second->populated, "Type " << dt.first << " never defined"); + } + + for(const auto& fcn : this->functions) + { + // TODO: This doesn't actually happen yet (this combination can't be parsed) + if( fcn.second.external.link_name != "" && !fcn.second.m_mir.blocks.empty() ) + { + LOG_DEBUG(fcn.first << " = '" << fcn.second.external.link_name << "'"); + ext_functions.insert(::std::make_pair( fcn.second.external.link_name, &fcn.second )); + } + } +} // Parse a single item from a .mir file bool Parser::parse_one() { @@ -99,25 +117,26 @@ bool Parser::parse_one() rv_ty = parse_type(); } + Function::ExtInfo ext; if( lex.consume_if('=') ) { - auto link_name = ::std::move(lex.check_consume(TokenClass::String).strval); + ext.link_name = ::std::move(lex.check_consume(TokenClass::String).strval); lex.check_consume(':'); - auto abi = ::std::move(lex.check_consume(TokenClass::String).strval); - lex.check_consume(';'); - + ext.link_abi = ::std::move(lex.check_consume(TokenClass::String).strval); + } + ::MIR::Function body; + if( lex.consume_if(';') ) + { LOG_DEBUG(lex << "extern fn " << p); - auto p2 = p; - tree.functions.insert( ::std::make_pair(::std::move(p), Function { ::std::move(p2), ::std::move(arg_tys), rv_ty, {link_name, abi}, {} }) ); } else { - auto body = parse_body(); + body = parse_body(); LOG_DEBUG(lex << "fn " << p); - auto p2 = p; - tree.functions.insert( ::std::make_pair(::std::move(p), Function { ::std::move(p2), ::std::move(arg_tys), rv_ty, {}, ::std::move(body) }) ); } + auto p2 = p; + tree.functions.insert( ::std::make_pair(::std::move(p), Function { ::std::move(p2), ::std::move(arg_tys), rv_ty, ::std::move(ext), ::std::move(body) }) ); } else if( lex.consume_if("static") ) { @@ -133,8 +152,7 @@ bool Parser::parse_one() Static s; s.val = Value(ty); // - Statics need to always have an allocation (for references) - if( !s.val.allocation ) - s.val.create_allocation(); + s.val.ensure_allocation(); s.val.write_bytes(0, data.data(), data.size()); s.ty = ty; @@ -153,15 +171,14 @@ bool Parser::parse_one() { auto reloc_str = ::std::move(lex.consume().strval); - auto a = Allocation::new_alloc( reloc_str.size() ); - //a.alloc().set_tag(); + auto a = Allocation::new_alloc( reloc_str.size(), FMT_STRING("static " << p) ); a->write_bytes(0, reloc_str.data(), reloc_str.size()); - s.val.allocation->relocations.push_back({ static_cast<size_t>(ofs), /*size,*/ RelocationPtr::new_alloc(::std::move(a)) }); + s.val.set_reloc( ofs, size, RelocationPtr::new_alloc(::std::move(a)) ); } else if( lex.next() == "::" || lex.next() == "<" ) { auto reloc_path = parse_path(); - s.val.allocation->relocations.push_back({ static_cast<size_t>(ofs), /*size,*/ RelocationPtr::new_fcn(reloc_path) }); + s.val.set_reloc( ofs, size, RelocationPtr::new_fcn(reloc_path) ); } else { @@ -185,6 +202,7 @@ bool Parser::parse_one() //LOG_TRACE("type " << p); auto rv = DataType {}; + rv.populated = true; rv.my_path = p; lex.check_consume('{'); @@ -332,9 +350,6 @@ bool Parser::parse_one() struct H { - static ::std::unique_ptr<::MIR::LValue> make_lvp(::MIR::LValue&& lv) { - return ::std::unique_ptr<::MIR::LValue>(new ::MIR::LValue(::std::move(lv))); - } // // Parse a LValue // @@ -357,7 +372,7 @@ bool Parser::parse_one() if( name.substr(0,3) == "arg" ) { try { auto idx = static_cast<unsigned>( ::std::stol(name.substr(3)) ); - lv = ::MIR::LValue::make_Argument({ idx }); + lv = ::MIR::LValue::new_Argument( idx ); } catch(const ::std::exception& e) { LOG_ERROR(lex << "Invalid argument name - " << name << " - " << e.what()); @@ -365,7 +380,7 @@ bool Parser::parse_one() } // Hard-coded "RETURN" lvalue else if( name == "RETURN" ) { - lv = ::MIR::LValue::make_Return({}); + lv = ::MIR::LValue::new_Return(); } // Otherwise, look up variable names else { @@ -373,13 +388,13 @@ bool Parser::parse_one() if( it == var_names.end() ) { LOG_ERROR(lex << "Cannot find variable named '" << name << "'"); } - lv = ::MIR::LValue::make_Local(static_cast<unsigned>(it - var_names.begin())); + lv = ::MIR::LValue::new_Local(static_cast<unsigned>(it - var_names.begin())); } } else if( lex.next() == "::" || lex.next() == '<' ) { auto path = p.parse_path(); - lv = ::MIR::LValue( ::std::move(path) ); + lv = ::MIR::LValue::new_Static( ::std::move(path) ); } else { LOG_ERROR(lex << "Unexpected token in LValue - " << lex.next()); @@ -390,19 +405,19 @@ bool Parser::parse_one() { lex.check(TokenClass::Integer); auto idx = static_cast<unsigned>( lex.consume().integer() ); - lv = ::MIR::LValue::make_Downcast({ make_lvp(::std::move(lv)), idx }); + lv = ::MIR::LValue::new_Downcast(::std::move(lv), idx); } else if( lex.consume_if('.') ) { lex.check(TokenClass::Integer); auto idx = static_cast<unsigned>( lex.consume().integer() ); - lv = ::MIR::LValue::make_Field({ make_lvp(::std::move(lv)), idx }); + lv = ::MIR::LValue::new_Field( ::std::move(lv), idx ); } else if( lex.next() == '[' ) { lex.consume(); auto idx_lv = parse_lvalue(p, var_names); - lv = ::MIR::LValue::make_Index({ make_lvp(::std::move(lv)), make_lvp(::std::move(idx_lv)) }); + lv = ::MIR::LValue::new_Index(::std::move(lv), idx_lv.as_Local()); lex.check_consume(']'); } else @@ -412,7 +427,7 @@ bool Parser::parse_one() } while(deref --) { - lv = ::MIR::LValue::make_Deref({ make_lvp(::std::move(lv)) }); + lv = ::MIR::LValue::new_Deref( ::std::move(lv) ); } return lv; } @@ -464,7 +479,7 @@ bool Parser::parse_one() else if( p.lex.consume_if("ADDROF") ) { auto path = p.parse_path(); - return ::MIR::Constant::make_ItemAddr({ ::std::move(path) }); + return ::MIR::Constant::make_ItemAddr({ ::std::make_unique<HIR::Path>(::std::move(path)) }); } else { LOG_BUG(p.lex << "BUG? " << p.lex.next()); @@ -867,7 +882,7 @@ bool Parser::parse_one() ::std::vector<unsigned> targets; while(lex.next() != '{') { - targets.push_back( static_cast<unsigned>(lex.consume().integer()) ); + targets.push_back( static_cast<unsigned>(lex.check_consume(TokenClass::Integer).integer()) ); if( !lex.consume_if(',') ) break; } @@ -936,7 +951,7 @@ bool Parser::parse_one() lex.check_consume(')'); } else if( lex.next() == TokenClass::String ) { - auto name = ::std::move(lex.consume().strval); + auto name = RcString::new_interned(lex.consume().strval); auto params = parse_pathparams(); ct = ::MIR::CallTarget::make_Intrinsic({ ::std::move(name), ::std::move(params) }); } @@ -1214,7 +1229,14 @@ RawType Parser::parse_core_type() { ret_ty = ::HIR::TypeRef::unit(); } - return ::HIR::TypeRef(RawType::Function); + auto ft = FunctionType { + is_unsafe, + ::std::move(abi), + ::std::move(args), + ::std::move(ret_ty) + }; + const auto* ft_p = &*tree.function_types.insert(::std::move(ft)).first; + return ::HIR::TypeRef(ft_p); // TODO: Use abi/ret_ty/args as part of that } else if( lex.consume_if("dyn") ) @@ -1262,14 +1284,28 @@ RawType Parser::parse_core_type() } lex.consume_if(')'); + // Ignore marker traits. + auto rv = ::HIR::TypeRef(RawType::TraitObject); if( base_trait != ::HIR::GenericPath() ) { // Generate vtable path auto vtable_path = base_trait; vtable_path.m_simplepath.ents.back() += "#vtable"; - // - TODO: Associated types? - rv.composite_type = this->get_composite( ::std::move(vtable_path) ); + if( atys.size() > 1 ) + { + LOG_TODO("Handle multiple ATYs in vtable path"); + } + else if( atys.size() == 1 ) + { + vtable_path.m_params.tys.push_back( ::std::move(atys[0].second) ); + } + // - TODO: Associated types? (Need to ensure ordering is correct) + rv.ptr.composite_type = this->get_composite( ::std::move(vtable_path) ); + } + else + { + // TODO: vtable for empty trait? } return rv; } @@ -1289,6 +1325,7 @@ const DataType* Parser::get_composite(::HIR::GenericPath gp) { // TODO: Later on need to check if the type is valid. auto v = ::std::make_unique<DataType>(DataType {}); + v->populated = false; v->my_path = gp; auto ir = tree.data_types.insert(::std::make_pair( ::std::move(gp), ::std::move(v)) ); it = ir.first; @@ -1318,6 +1355,15 @@ const Function* ModuleTree::get_function_opt(const ::HIR::Path& p) const } return &it->second; } +const Function* ModuleTree::get_ext_function(const char* name) const +{ + auto it = ext_functions.find(name); + if( it == ext_functions.end() ) + { + return nullptr; + } + return it->second; +} Static& ModuleTree::get_static(const ::HIR::Path& p) { auto it = statics.find(p); diff --git a/tools/standalone_miri/module_tree.hpp b/tools/standalone_miri/module_tree.hpp index efa0a034..299aa51c 100644 --- a/tools/standalone_miri/module_tree.hpp +++ b/tools/standalone_miri/module_tree.hpp @@ -11,6 +11,7 @@ #include <map> #include <set> +#include "../../src/include/rc_string.hpp" #include "../../src/mir/mir.hpp" #include "hir_sim.hpp" #include "value.hpp" @@ -22,7 +23,7 @@ struct Function ::HIR::TypeRef ret_ty; // If `link_name` is non-empty, then the function is an external - struct { + struct ExtInfo { ::std::string link_name; ::std::string link_abi; } external; @@ -47,14 +48,20 @@ class ModuleTree // Hack: Tuples are stored as `::""::<A,B,C,...>` ::std::map<::HIR::GenericPath, ::std::unique_ptr<DataType>> data_types; + + ::std::set<FunctionType> function_types; // note: insertion doesn't invaliate pointers. + + ::std::map<::std::string, const Function*> ext_functions; public: ModuleTree(); void load_file(const ::std::string& path); + void validate(); ::HIR::SimplePath find_lang_item(const char* name) const; const Function& get_function(const ::HIR::Path& p) const; const Function* get_function_opt(const ::HIR::Path& p) const; + const Function* get_ext_function(const char* name) const; Static& get_static(const ::HIR::Path& p); Static* get_static_opt(const ::HIR::Path& p); @@ -66,16 +73,15 @@ public: // struct/union/enum struct DataType { + bool populated; ::HIR::GenericPath my_path; - // TODO: Store the name of this type for logging? - - // TODO: Metadata type! (indicates an unsized wrapper) - // TODO: Drop glue size_t alignment; size_t size; + // Drop glue ::HIR::Path drop_glue; + // Metadata type! (indicates an unsized wrapper) ::HIR::TypeRef dst_meta; // Offset and datatype @@ -91,3 +97,21 @@ struct DataType }; ::std::vector<VariantValue> variants; }; + +struct FunctionType +{ + bool unsafe; + ::std::string abi; + ::std::vector<HIR::TypeRef> args; + HIR::TypeRef ret; + + bool operator<(const FunctionType& x) const { + #define _(f) if(f != x.f) return f < x.f + _(unsafe); + _(abi); + _(args); + _(ret); + #undef _ + return false; + } +}; diff --git a/tools/standalone_miri/u128.hpp b/tools/standalone_miri/u128.hpp new file mode 100644 index 00000000..8403b94a --- /dev/null +++ b/tools/standalone_miri/u128.hpp @@ -0,0 +1,170 @@ +#pragma once + +class U128 +{ + friend class I128; + uint64_t lo, hi; +public: + U128(): lo(0), hi(0) {} + + U128(uint8_t v): lo(v), hi(0) {} + U128(int8_t v): lo(v), hi(v < 0 ? -1 : 0) {} + + void fmt(::std::ostream& os) const { os << hi << ":" << lo; } + + int cmp(U128 v) const { + if( hi != v.hi ) { + return (hi < v.hi ? -1 : 1); + } + if( lo != v.lo ) { + return (lo < v.lo ? -1 : 1); + } + return 0; + } + int cmp(unsigned v) const { + if(hi) + return 1; + if(lo < v) + return -1; + if(lo > v) + return 1; + return 0; + } + + template<typename T> bool operator< (const T& v) const { return this->cmp(v) < 0; } + template<typename T> bool operator<=(const T& v) const { return this->cmp(v) <= 0; } + template<typename T> bool operator> (const T& v) const { return this->cmp(v) > 0; } + template<typename T> bool operator>=(const T& v) const { return this->cmp(v) >= 0; } + template<typename T> bool operator==(const T& v) const { return this->cmp(v) == 0; } + template<typename T> bool operator!=(const T& v) const { return this->cmp(v) != 0; } + + operator uint8_t() const { return static_cast<uint8_t>(lo); } + + U128 operator&(U128 x) const { + U128 rv; + rv.lo = this->lo & x.lo; + rv.hi = this->hi & x.hi; + return rv; + } + U128 operator|(U128 x) const { + U128 rv; + rv.lo = this->lo | x.lo; + rv.hi = this->hi | x.hi; + return rv; + } + U128 operator^(U128 x) const { + U128 rv; + rv.lo = this->lo ^ x.lo; + rv.hi = this->hi ^ x.hi; + return rv; + } + + U128 operator<<(U128 n) const + { + if( n < 128 ) + { + return *this << static_cast<uint8_t>(n); + } + else + { + return U128(); + } + } + U128 operator<<(uint8_t n) const + { + if(n == 0) + { + return *this; + } + else if( n < 64 ) + { + U128 rv; + rv.lo = lo << n; + rv.hi = (hi << n) | (lo >> (64-n)); + return rv; + } + else if( n < 128 ) + { + U128 rv; + rv.lo = 0; + rv.hi = (lo << (n-64)); + return rv; + } + else + { + return U128(); + } + } + U128 operator>>(uint8_t n) const + { + if(n == 0) + { + return *this; + } + else if( n < 64 ) + { + U128 rv; + rv.lo = (lo >> n) | (hi << (64-n)); + rv.hi = (hi >> n); + return rv; + } + else if( n < 128 ) + { + U128 rv; + rv.lo = (hi >> (n-64)); + rv.hi = 0; + return rv; + } + else + { + return U128(); + } + } + + friend ::std::ostream& operator<<(::std::ostream& os, const U128& x) { x.fmt(os); return os; } +}; + +class I128 +{ + U128 v; +public: + I128() {} + + int cmp(I128 x) const { + if(v.hi != x.v.hi) + return (static_cast<int64_t>(v.hi) < static_cast<int64_t>(x.v.hi) ? -1 : 1); + if(v.lo != x.v.lo) + { + if( static_cast<int64_t>(v.hi) < 0 ) + { + // Negative, so larger raw value is the smaller + return (v.lo > x.v.lo ? -1 : 1); + } + else + { + return (v.lo < x.v.lo ? -1 : 1); + } + } + return 0; + } + //int cmp(int v) const { + // if(hi) + // return 1; + // if(lo < v) + // return -1; + // if(lo > v) + // return 1; + // return 0; + //} + + template<typename T> bool operator< (const T& v) const { return this->cmp(v) < 0; } + template<typename T> bool operator<=(const T& v) const { return this->cmp(v) <= 0; } + template<typename T> bool operator> (const T& v) const { return this->cmp(v) > 0; } + template<typename T> bool operator>=(const T& v) const { return this->cmp(v) >= 0; } + template<typename T> bool operator==(const T& v) const { return this->cmp(v) == 0; } + template<typename T> bool operator!=(const T& v) const { return this->cmp(v) != 0; } + + void fmt(::std::ostream& os) const { os << v.hi << ":" << v.lo; } + + friend ::std::ostream& operator<<(::std::ostream& os, const I128& x) { x.fmt(os); return os; } +}; diff --git a/tools/standalone_miri/value.cpp b/tools/standalone_miri/value.cpp index 5974a172..a497f0bd 100644 --- a/tools/standalone_miri/value.cpp +++ b/tools/standalone_miri/value.cpp @@ -13,13 +13,98 @@ #include <algorithm> #include "debug.hpp" -AllocationHandle Allocation::new_alloc(size_t size) +namespace { + static bool in_bounds(size_t ofs, size_t size, size_t max_size) { + if( !(ofs < max_size) ) + return false; + if( !(size <= max_size) ) + return false; + return ofs + size <= max_size; + } + + void set_bit(uint8_t* p, size_t i, bool v) + { + if(v) { + p[i/8] |= 1 << (i%8); + } + else { + p[i/8] &= ~(1 << (i%8)); + } + } + bool get_bit(const uint8_t* p, size_t i) { + return (p[i/8] & (1 << (i%8))) != 0; + } + void copy_bits(uint8_t* dst, size_t dst_ofs, const uint8_t* src, size_t src_ofs, size_t len) + { + // Even number of bytes, fast copy + if( dst_ofs % 8 == 0 && src_ofs % 8 == 0 && len % 8 == 0 ) + { + for(size_t i = 0; i < len/8; i ++) + { + dst[dst_ofs/8 + i] = src[src_ofs/8 + i]; + } + } + else + { + for(size_t i = 0; i < len; i ++) + { + set_bit( dst, dst_ofs+i, get_bit(src, src_ofs+i) ); + } + } + } +}; + +::std::ostream& operator<<(::std::ostream& os, const Allocation* x) +{ + os << "A(#" << x->m_index << " " << x->tag() /*<< " +" << x->size()*/ << ")"; + return os; +} + +FfiLayout FfiLayout::new_const_bytes(size_t s) +{ + return FfiLayout { + { Range {s, true, false} } + }; +} +bool FfiLayout::is_valid_read(size_t o, size_t s) const +{ + for(const auto& r : ranges) + { + if( o < r.len ) { + if( !r.is_valid ) + return false; + if( o + s <= r.len ) + { + s = 0; + break; + } + s -= (r.len - o); + o = 0; + } + else { + o -= r.len; + } + } + if( s > 0 ) + { + return false; + } + return true; +} + +uint64_t Allocation::s_next_index = 0; + +AllocationHandle Allocation::new_alloc(size_t size, ::std::string tag) { Allocation* rv = new Allocation(); + rv->m_index = s_next_index++; + rv->m_tag = ::std::move(tag); rv->refcount = 1; - rv->data.resize( (size + 8-1) / 8 ); // QWORDS - rv->mask.resize( (size + 8-1) / 8 ); // bitmap bytes + rv->m_size = size; + rv->m_data.resize( (size + 8-1) / 8 ); // QWORDS + rv->m_mask.resize( (size + 8-1) / 8 ); // bitmap bytes //LOG_DEBUG(rv << " ALLOC"); + LOG_DEBUG(rv); return AllocationHandle(rv); } AllocationHandle::AllocationHandle(const AllocationHandle& x): @@ -167,7 +252,7 @@ size_t RelocationPtr::get_size() const os << "\"" << x.str() << "\""; break; case RelocationPtr::Ty::FfiPointer: - os << "FFI " << x.ffi().source_function << " " << x.ffi().ptr_value; + os << "FFI '" << x.ffi().tag_name << "' " << x.ffi().ptr_value; break; } } @@ -191,6 +276,8 @@ void ValueCommonWrite::write_usize(size_t ofs, uint64_t v) void* ValueCommonRead::read_pointer_unsafe(size_t rd_ofs, size_t req_valid, size_t& out_size, bool& out_is_mut) const { auto ofs = read_usize(rd_ofs); + LOG_ASSERT(ofs >= Allocation::PTR_BASE, "Deref of invalid pointer"); + ofs -= Allocation::PTR_BASE; auto reloc = get_relocation(rd_ofs); if( !reloc ) { @@ -210,22 +297,16 @@ void* ValueCommonRead::read_pointer_unsafe(size_t rd_ofs, size_t req_valid, size { case RelocationPtr::Ty::Allocation: { auto& a = reloc.alloc(); - if( ofs > a.size() ) - LOG_FATAL("Out-of-bounds pointer"); - if( ofs + req_valid > a.size() ) - LOG_FATAL("Out-of-bounds pointer (" << ofs << " + " << req_valid << " > " << a.size()); - a.check_bytes_valid( static_cast<size_t>(ofs), req_valid ); - out_size = a.size() - static_cast<size_t>(ofs); + LOG_ASSERT(in_bounds(ofs, req_valid, a.size()), "Out-of-bounds pointer (" << ofs << " + " << req_valid << " > " << a.size() << ")"); + a.check_bytes_valid( ofs, req_valid ); + out_size = a.size() - ofs; out_is_mut = true; return a.data_ptr() + ofs; } case RelocationPtr::Ty::StdString: { const auto& s = reloc.str(); - if( ofs > s.size() ) - LOG_FATAL("Out-of-bounds pointer"); - if( ofs + req_valid > s.size() ) - LOG_FATAL("Out-of-bounds pointer (" << ofs << " + " << req_valid << " > " << s.size()); - out_size = s.size() - static_cast<size_t>(ofs); + LOG_ASSERT(in_bounds(ofs, req_valid, s.size()), "Out-of-bounds pointer (" << ofs << " + " << req_valid << " > " << s.size() << ")"); + out_size = s.size() - ofs; out_is_mut = false; return const_cast<void*>( static_cast<const void*>(s.data() + ofs) ); } @@ -233,11 +314,13 @@ void* ValueCommonRead::read_pointer_unsafe(size_t rd_ofs, size_t req_valid, size LOG_FATAL("read_pointer w/ function"); case RelocationPtr::Ty::FfiPointer: { const auto& f = reloc.ffi(); + size_t size = f.get_size(); + LOG_ASSERT(in_bounds(ofs, req_valid, size), "Out-of-bounds pointer (" << ofs << " + " << req_valid << " > " << size << ")"); // TODO: Validity? //if( req_valid ) // LOG_FATAL("Can't request valid data from a FFI pointer"); // TODO: Have an idea of mutability and available size from FFI - out_size = f.size - static_cast<size_t>(ofs); + out_size = size - ofs; out_is_mut = false; return reinterpret_cast<char*>(reloc.ffi().ptr_value) + ofs; } @@ -248,17 +331,38 @@ void* ValueCommonRead::read_pointer_unsafe(size_t rd_ofs, size_t req_valid, size ValueRef ValueCommonRead::read_pointer_valref_mut(size_t rd_ofs, size_t size) { auto ofs = read_usize(rd_ofs); + LOG_ASSERT(ofs >= Allocation::PTR_BASE, "Invalid pointer read"); + ofs -= Allocation::PTR_BASE; auto reloc = get_relocation(rd_ofs); + LOG_DEBUG("ValueCommonRead::read_pointer_valref_mut(" << ofs << "+" << size << ", reloc=" << reloc << ")"); if( !reloc ) { LOG_ERROR("Getting ValRef to null pointer (no relocation)"); } else { - // TODO: Validate size - return ValueRef(reloc, static_cast<size_t>(ofs), size); + // Validate size and offset are in bounds + switch(reloc.get_ty()) + { + case RelocationPtr::Ty::Allocation: + LOG_ASSERT( in_bounds(ofs, size, reloc.alloc().size()), "Deref with OOB size - " << ofs << "+" << size << " > " << reloc.alloc().size() ); + break; + case RelocationPtr::Ty::StdString: + LOG_ASSERT( in_bounds(ofs, size, reloc.str().size()), "Deref with OOB size - " << ofs << "+" << size << " > " << reloc.str().size() ); + break; + case RelocationPtr::Ty::Function: + LOG_FATAL("read_pointer_valref_mut w/ function"); + case RelocationPtr::Ty::FfiPointer: + LOG_ASSERT( in_bounds(ofs, size, reloc.ffi().get_size()), "Deref with OOB size - " << ofs << "+" << size << " > " << reloc.ffi().get_size() ); + break; + } + return ValueRef(reloc, ofs, size); } } +ValueRef ValueCommonRead::deref(size_t ofs, const ::HIR::TypeRef& ty) +{ + return read_pointer_valref_mut(ofs, ty.get_size()); +} void Allocation::resize(size_t new_size) @@ -268,18 +372,19 @@ void Allocation::resize(size_t new_size) //size_t old_size = this->size(); //size_t extra_bytes = (new_size > old_size ? new_size - old_size : 0); - this->data.resize( (new_size + 8-1) / 8 ); - this->mask.resize( (new_size + 8-1) / 8 ); + this->m_size = new_size; + this->m_data.resize( (new_size + 8-1) / 8 ); + this->m_mask.resize( (new_size + 8-1) / 8 ); } void Allocation::check_bytes_valid(size_t ofs, size_t size) const { - if( !(ofs + size <= this->size()) ) { + if( !in_bounds(ofs, size, this->size()) ) { LOG_FATAL("Out of range - " << ofs << "+" << size << " > " << this->size()); } for(size_t i = ofs; i < ofs + size; i++) { - if( !(this->mask[i/8] & (1 << (i%8))) ) + if( !(this->m_mask[i/8] & (1 << (i%8))) ) { LOG_ERROR("Invalid bytes in value - " << ofs << "+" << size << " - " << *this); throw "ERROR"; @@ -288,19 +393,20 @@ void Allocation::check_bytes_valid(size_t ofs, size_t size) const } void Allocation::mark_bytes_valid(size_t ofs, size_t size) { - assert( ofs+size <= this->mask.size() * 8 ); + assert( ofs+size <= this->m_mask.size() * 8 ); for(size_t i = ofs; i < ofs + size; i++) { - this->mask[i/8] |= (1 << (i%8)); + this->m_mask[i/8] |= (1 << (i%8)); } } Value Allocation::read_value(size_t ofs, size_t size) const { Value rv; - TRACE_FUNCTION_R("Allocation::read_value " << this << " " << ofs << "+" << size, *this << " | " << rv); + //TRACE_FUNCTION_R("Allocation::read_value " << this << " " << ofs << "+" << size, *this << " | " << size << "=" << rv); if( this->is_freed ) LOG_ERROR("Use of freed memory " << this); LOG_DEBUG(*this); + LOG_ASSERT( in_bounds(ofs, size, this->size()), "Read out of bounds (" << ofs << "+" << size << " > " << this->size() << ")" ); // Determine if this can become an inline allocation. bool has_reloc = false; @@ -308,57 +414,25 @@ Value Allocation::read_value(size_t ofs, size_t size) const { if( ofs <= r.slot_ofs && r.slot_ofs < ofs + size ) { + // NOTE: A relocation at offset zero is allowed + if( r.slot_ofs == ofs ) + continue ; has_reloc = true; } } - if( has_reloc || size > sizeof(rv.direct_data.data) ) - { - rv.allocation = Allocation::new_alloc(size); - - rv.write_bytes(0, this->data_ptr() + ofs, size); - - for(const auto& r : this->relocations) - { - if( ofs <= r.slot_ofs && r.slot_ofs < ofs + size ) - { - rv.allocation->relocations.push_back({ r.slot_ofs - ofs, r.backing_alloc }); - } - } + rv = Value::with_size(size, has_reloc); + rv.write_bytes(0, this->data_ptr() + ofs, size); - // Copy the mask bits - for(size_t i = 0; i < size; i ++) - { - size_t j = ofs + i; - const uint8_t test_mask = (1 << (j%8)); - const uint8_t set_mask = (1 << (i%8)); - bool v = (this->mask[j/8] & test_mask) != 0; - if( v ) - { - rv.allocation->mask[i/8] |= set_mask; - } - } - } - else + for(const auto& r : this->relocations) { - rv.direct_data.size = static_cast<uint8_t>(size); - - rv.write_bytes(0, this->data_ptr() + ofs, size); - rv.direct_data.mask[0] = 0; - rv.direct_data.mask[1] = 0; - - // Copy the mask bits - for(size_t i = 0; i < size; i ++) + if( ofs <= r.slot_ofs && r.slot_ofs < ofs + size ) { - size_t j = ofs + i; - const uint8_t tst_mask = 1 << (j%8); - const uint8_t set_mask = 1 << (i%8); - bool v = (this->mask[j/8] & tst_mask) != 0; - if( v ) - { - rv.direct_data.mask[i/8] |= set_mask; - } + rv.set_reloc(r.slot_ofs - ofs, /*r.size*/POINTER_SIZE, r.backing_alloc); } } + // Copy the mask bits + copy_bits(rv.get_mask_mut(), 0, m_mask.data(), ofs, size); + return rv; } void Allocation::read_bytes(size_t ofs, void* dst, size_t count) const @@ -366,19 +440,11 @@ void Allocation::read_bytes(size_t ofs, void* dst, size_t count) const if( this->is_freed ) LOG_ERROR("Use of freed memory " << this); - LOG_DEBUG("Allocation::read_bytes " << this << " " << ofs << "+" << count); + //LOG_DEBUG("Allocation::read_bytes " << this << " " << ofs << "+" << count); if(count == 0) return ; - if(ofs >= this->size() ) { - LOG_ERROR("Out of bounds read, " << ofs << "+" << count << " > " << this->size()); - throw "ERROR"; - } - if(count > this->size() ) { - LOG_ERROR("Out of bounds read, " << ofs << "+" << count << " > " << this->size()); - throw "ERROR"; - } - if(ofs+count > this->size() ) { + if( !in_bounds(ofs, count, this->size()) ) { LOG_ERROR("Out of bounds read, " << ofs << "+" << count << " > " << this->size()); throw "ERROR"; } @@ -394,14 +460,15 @@ void Allocation::write_value(size_t ofs, Value v) LOG_ERROR("Use of freed memory " << this); //if( this->is_read_only ) // LOG_ERROR("Writing to read-only allocation " << this); - if( v.allocation ) + if( v.m_inner.is_alloc ) { - size_t v_size = v.allocation->size(); - const auto& src_alloc = *v.allocation; - // Take a copy of the source mask - auto s_mask = src_alloc.mask; + const auto& src_alloc = *v.m_inner.alloc.alloc; + size_t v_size = src_alloc.size(); + assert(&src_alloc != this); // Shouldn't happen? - // Save relocations first, because `Foo = Foo` is valid. + // Take a copy of the source mask + auto s_mask = src_alloc.m_mask; + // Save relocations first, because `Foo = Foo` is valid? ::std::vector<Relocation> new_relocs = src_alloc.relocations; // - write_bytes removes any relocations in this region. write_bytes(ofs, src_alloc.data_ptr(), v_size); @@ -420,39 +487,16 @@ void Allocation::write_value(size_t ofs, Value v) } // Set mask in destination - if( ofs % 8 != 0 || v_size % 8 != 0 ) - { - // Lazy way, sets/clears individual bits - for(size_t i = 0; i < v_size; i ++) - { - uint8_t dbit = 1 << ((ofs+i) % 8); - if( s_mask[i/8] & (1 << (i %8)) ) - this->mask[ (ofs+i) / 8 ] |= dbit; - else - this->mask[ (ofs+i) / 8 ] &= ~dbit; - } - } - else - { - // Copy the mask bytes directly - for(size_t i = 0; i < v_size / 8; i ++) - { - this->mask[ofs/8+i] = s_mask[i]; - } - } + copy_bits(m_mask.data(), ofs, s_mask.data(), 0, v_size); } else { - this->write_bytes(ofs, v.direct_data.data, v.direct_data.size); - - // Lazy way, sets/clears individual bits - for(size_t i = 0; i < v.direct_data.size; i ++) + this->write_bytes(ofs, v.data_ptr(), v.size()); + copy_bits(m_mask.data(), ofs, v.get_mask(), 0, v.size()); + // TODO: Copy relocation + if( v.m_inner.direct.reloc_0 ) { - uint8_t dbit = 1 << ((ofs+i) % 8); - if( v.direct_data.mask[i/8] & (1 << (i %8)) ) - this->mask[ (ofs+i) / 8 ] |= dbit; - else - this->mask[ (ofs+i) / 8 ] &= ~dbit; + this->set_reloc(ofs, POINTER_SIZE, ::std::move(v.m_inner.direct.reloc_0)); } } } @@ -466,16 +510,8 @@ void Allocation::write_bytes(size_t ofs, const void* src, size_t count) if(count == 0) return ; - TRACE_FUNCTION_R("Allocation::write_bytes " << this << " " << ofs << "+" << count, *this); - if(ofs >= this->size() ) { - LOG_ERROR("Out of bounds write, " << ofs << "+" << count << " > " << this->size()); - throw "ERROR"; - } - if(count > this->size() ) { - LOG_ERROR("Out of bounds write, " << ofs << "+" << count << " > " << this->size()); - throw "ERROR"; - } - if(ofs+count > this->size() ) { + //TRACE_FUNCTION_R("Allocation::write_bytes " << this << " " << ofs << "+" << count, *this); + if( !in_bounds(ofs, count, this->size()) ) { LOG_ERROR("Out of bounds write, " << ofs << "+" << count << " > " << this->size()); throw "ERROR"; } @@ -501,8 +537,29 @@ void Allocation::write_bytes(size_t ofs, const void* src, size_t count) } void Allocation::write_ptr(size_t ofs, size_t ptr_ofs, RelocationPtr reloc) { + LOG_ASSERT(ptr_ofs >= Allocation::PTR_BASE, "Invalid pointer being written"); this->write_usize(ofs, ptr_ofs); - this->relocations.push_back(Relocation { ofs, /*POINTER_SIZE,*/ ::std::move(reloc) }); + this->set_reloc(ofs, POINTER_SIZE, ::std::move(reloc)); +} +void Allocation::set_reloc(size_t ofs, size_t len, RelocationPtr reloc) +{ + LOG_ASSERT(ofs % POINTER_SIZE == 0, ""); + LOG_ASSERT(len == POINTER_SIZE, ""); + // Delete any existing relocation at this position + for(auto it = this->relocations.begin(); it != this->relocations.end();) + { + if( ofs <= it->slot_ofs && it->slot_ofs < ofs + len ) + { + // Slot starts in this updated region + // - TODO: Split in half? + it = this->relocations.erase(it); + continue ; + } + // TODO: What if the slot ends in the new region? + // What if the new region is in the middle of the slot + ++ it; + } + this->relocations.push_back(Relocation { ofs, /*len,*/ ::std::move(reloc) }); } ::std::ostream& operator<<(::std::ostream& os, const Allocation& x) { @@ -513,7 +570,7 @@ void Allocation::write_ptr(size_t ofs, size_t ptr_ofs, RelocationPtr reloc) if( i != 0 ) os << " "; - if( x.mask[i/8] & (1 << (i%8)) ) + if( x.m_mask[i/8] & (1 << (i%8)) ) { os << ::std::setw(2) << ::std::setfill('0') << (int)x.data_ptr()[i]; } @@ -538,72 +595,62 @@ void Allocation::write_ptr(size_t ofs, size_t ptr_ofs, RelocationPtr reloc) Value::Value() { - this->direct_data.size = 0; - this->direct_data.mask[0] = 0; - this->direct_data.mask[1] = 0; + memset(&m_inner, 0, sizeof(m_inner)); } Value::Value(::HIR::TypeRef ty) { size_t size = ty.get_size(); // Support inline data if the data will fit within the inline region (which is the size of the metadata) - if( ty.get_size() <= sizeof(this->direct_data.data) ) + if( size <= sizeof(m_inner.direct.data) ) { // AND the type doesn't contain a pointer (of any kind) - if( ! ty.has_pointer() ) + // TODO: Pointers _are_ allowed now (but only one) + if( true || ! ty.has_pointer() ) { // Will fit in a inline allocation, nice. //LOG_TRACE("No pointers in " << ty << ", storing inline"); - this->direct_data.size = static_cast<uint8_t>(size); - this->direct_data.mask[0] = 0; - this->direct_data.mask[1] = 0; + new(&m_inner.direct) Inner::Direct(size); return ; } } // Fallback: Make a new allocation //LOG_TRACE(" Creating allocation for " << ty); - this->allocation = Allocation::new_alloc(size); + new(&m_inner.alloc) Inner::Alloc( Allocation::new_alloc(size, FMT_STRING(ty)) ); + assert(m_inner.is_alloc); } Value Value::with_size(size_t size, bool have_allocation) { Value rv; - if(have_allocation) + if(have_allocation || size > sizeof(m_inner.direct.data)) { - rv.allocation = Allocation::new_alloc(size); + new(&rv.m_inner.alloc) Inner::Alloc( Allocation::new_alloc(size, FMT_STRING("with_size(" << size << ")")) ); } else { - rv.direct_data.size = static_cast<uint8_t>(size); - rv.direct_data.mask[0] = 0; - rv.direct_data.mask[1] = 0; + new(&rv.m_inner.direct) Inner::Direct(size); } return rv; } Value Value::new_fnptr(const ::HIR::Path& fn_path) { Value rv( ::HIR::TypeRef(::HIR::CoreType { RawType::Function }) ); - assert(rv.allocation); - rv.allocation->relocations.push_back(Relocation { 0, RelocationPtr::new_fcn(fn_path) }); - rv.allocation->data.at(0) = 0; - rv.allocation->mask.at(0) = (1 << POINTER_SIZE)-1; + rv.write_ptr(0, Allocation::PTR_BASE, RelocationPtr::new_fcn(fn_path)); return rv; } Value Value::new_ffiptr(FFIPointer ffi) { Value rv( ::HIR::TypeRef(::HIR::CoreType { RawType::USize }) ); - rv.create_allocation(); - rv.allocation->relocations.push_back(Relocation { 0, RelocationPtr::new_ffi(ffi) }); - rv.allocation->data.at(0) = 0; - rv.allocation->mask.at(0) = (1 << POINTER_SIZE)-1; + assert( !rv.m_inner.is_alloc ); + rv.write_ptr(0, Allocation::PTR_BASE, RelocationPtr::new_ffi(ffi)); return rv; } Value Value::new_pointer(::HIR::TypeRef ty, uint64_t v, RelocationPtr r) { assert(ty.get_wrapper()); assert(ty.get_wrapper()->type == TypeWrapper::Ty::Borrow || ty.get_wrapper()->type == TypeWrapper::Ty::Pointer); Value rv(ty); - rv.write_usize(0, v); - rv.allocation->relocations.push_back(Relocation { 0, /*POINTER_SIZE,*/ ::std::move(r) }); + rv.write_ptr(0, v, ::std::move(r)); return rv; } Value Value::new_usize(uint64_t v) { @@ -626,59 +673,57 @@ Value Value::new_i32(int32_t v) { rv.write_i32(0, v); return rv; } +Value Value::new_i64(int64_t v) { + auto rv = Value( ::HIR::TypeRef(RawType::I64) ); + rv.write_i64(0, v); + return rv; +} void Value::create_allocation() { - assert(!this->allocation); - this->allocation = Allocation::new_alloc(this->direct_data.size); - if( this->direct_data.size > 0 ) - this->allocation->mask[0] = this->direct_data.mask[0]; - if( this->direct_data.size > 8 ) - this->allocation->mask[1] = this->direct_data.mask[1]; - ::std::memcpy(this->allocation->data.data(), this->direct_data.data, this->direct_data.size); + assert(!m_inner.is_alloc); + auto new_alloc = Allocation::new_alloc(m_inner.direct.size, "create_allocation"); // TODO: Provide a better name? + auto& direct = m_inner.direct; + if( direct.size > 0 ) + new_alloc->m_mask[0] = direct.mask[0]; + if( direct.size > 8 ) + new_alloc->m_mask[1] = direct.mask[1]; + ::std::memcpy(new_alloc->data_ptr(), direct.data, direct.size); + if( direct.reloc_0 ) + { + new_alloc->set_reloc(0, POINTER_SIZE, ::std::move(direct.reloc_0)); + } + + new(&m_inner.alloc) Inner::Alloc(::std::move(new_alloc)); } void Value::check_bytes_valid(size_t ofs, size_t size) const { if( size == 0 ) return ; - if( this->allocation ) - { - this->allocation->check_bytes_valid(ofs, size); + if( !in_bounds(ofs, size, this->size()) ) { + LOG_ERROR("Read out of bounds " << ofs+size << " >= " << this->size()); + throw "ERROR"; } - else + const auto* mask = this->get_mask(); + for(size_t i = ofs; i < ofs + size; i++) { - if( size == 0 && this->direct_data.size > 0 ) { - return ; - } - if( ofs >= this->direct_data.size ) { - LOG_ERROR("Read out of bounds " << ofs << "+" << size << " > " << int(this->direct_data.size)); - throw "ERROR"; - } - if( ofs+size > this->direct_data.size ) { - LOG_ERROR("Read out of bounds " << ofs+size << " >= " << int(this->direct_data.size)); - throw "ERROR"; - } - for(size_t i = ofs; i < ofs + size; i++) + if( !get_bit(mask, i) ) { - if( !(this->direct_data.mask[i/8] & (1 << i%8)) ) - { - LOG_ERROR("Accessing invalid bytes in value"); - throw "ERROR"; - } + LOG_ERROR("Accessing invalid bytes in value, offset " << i << " of " << *this); } } } void Value::mark_bytes_valid(size_t ofs, size_t size) { - if( this->allocation ) + if( m_inner.is_alloc ) { - this->allocation->mark_bytes_valid(ofs, size); + m_inner.alloc.alloc->mark_bytes_valid(ofs, size); } else { for(size_t i = ofs; i < ofs+size; i++) { - this->direct_data.mask[i/8] |= (1 << i%8); + m_inner.direct.mask[i/8] |= (1 << i%8); } } } @@ -686,18 +731,28 @@ void Value::mark_bytes_valid(size_t ofs, size_t size) Value Value::read_value(size_t ofs, size_t size) const { Value rv; - //TRACE_FUNCTION_R(ofs << ", " << size << ") - " << *this, rv); - if( this->allocation ) + TRACE_FUNCTION_R(ofs << ", " << size << " - " << *this, rv); + if( m_inner.is_alloc ) { - rv = this->allocation->read_value(ofs, size); + rv = m_inner.alloc.alloc->read_value(ofs, size); } else { - // Inline can become inline. - rv.direct_data.size = static_cast<uint8_t>(size); - rv.write_bytes(0, this->direct_data.data+ofs, size); - rv.direct_data.mask[0] = this->direct_data.mask[0]; - rv.direct_data.mask[1] = this->direct_data.mask[1]; + // Inline always fits in inline. + if( ofs == 0 && size == this->size() ) + { + rv = Value(*this); + } + else + { + rv.m_inner.direct.size = static_cast<uint8_t>(size); + memcpy(rv.m_inner.direct.data, this->data_ptr() + ofs, size); + copy_bits(rv.m_inner.direct.mask, 0, this->get_mask(), ofs, size); + if( ofs == 0 ) + { + rv.m_inner.direct.reloc_0 = RelocationPtr(m_inner.direct.reloc_0); + } + } } return rv; } @@ -705,27 +760,14 @@ void Value::read_bytes(size_t ofs, void* dst, size_t count) const { if(count == 0) return ; - if( this->allocation ) + if( m_inner.is_alloc ) { - this->allocation->read_bytes(ofs, dst, count); + m_inner.alloc.alloc->read_bytes(ofs, dst, count); } else { check_bytes_valid(ofs, count); - - if(ofs >= this->direct_data.size ) { - LOG_ERROR("Out of bounds read, " << ofs << "+" << count << " > " << this->size()); - throw "ERROR"; - } - if(count > this->direct_data.size ) { - LOG_ERROR("Out of bounds read, " << ofs << "+" << count << " > " << this->size()); - throw "ERROR"; - } - if(ofs+count > this->direct_data.size ) { - LOG_ERROR("Out of bounds read, " << ofs << "+" << count << " > " << this->size()); - throw "ERROR"; - } - ::std::memcpy(dst, this->direct_data.data + ofs, count); + ::std::memcpy(dst, m_inner.direct.data + ofs, count); } } @@ -733,88 +775,65 @@ void Value::write_bytes(size_t ofs, const void* src, size_t count) { if( count == 0 ) return ; - if( this->allocation ) + if( m_inner.is_alloc ) { - this->allocation->write_bytes(ofs, src, count); + m_inner.alloc.alloc->write_bytes(ofs, src, count); } else { - if(ofs >= this->direct_data.size ) { - LOG_BUG("Write to offset outside value size (" << ofs << "+" << count << " >= " << (int)this->direct_data.size << ")"); + auto& direct = m_inner.direct; + if( !in_bounds(ofs, count, direct.size) ) { + LOG_ERROR("Write extends outside value size (" << ofs << "+" << count << " >= " << (int)direct.size << ")"); } - if(count > this->direct_data.size ){ - LOG_BUG("Write larger than value size (" << ofs << "+" << count << " >= " << (int)this->direct_data.size << ")"); - } - if(ofs+count > this->direct_data.size ) { - LOG_BUG("Write extends outside value size (" << ofs << "+" << count << " >= " << (int)this->direct_data.size << ")"); - } - ::std::memcpy(this->direct_data.data + ofs, src, count); + ::std::memcpy(direct.data + ofs, src, count); mark_bytes_valid(ofs, count); + if( 0 <= ofs && ofs < POINTER_SIZE ) { + direct.reloc_0 = RelocationPtr(); + } } } void Value::write_value(size_t ofs, Value v) { - if( this->allocation ) + if( m_inner.is_alloc ) { - this->allocation->write_value(ofs, ::std::move(v)); + m_inner.alloc.alloc->write_value(ofs, ::std::move(v)); } else { - if( v.allocation && !v.allocation->relocations.empty() ) - { - this->create_allocation(); - this->allocation->write_value(ofs, ::std::move(v)); - } - else - { - write_bytes(ofs, v.direct_data.data, v.direct_data.size); + write_bytes(ofs, v.data_ptr(), v.size()); + // - Copy mask + copy_bits(this->get_mask_mut(), ofs, v.get_mask(), 0, v.size()); - // Lazy way, sets/clears individual bits - for(size_t i = 0; i < v.direct_data.size; i ++) + // TODO: Faster way of knowing where there are relocations + for(size_t i = 0; i < v.size(); i ++) + { + if( auto r = v.get_relocation(i) ) { - uint8_t dbit = 1 << ((ofs+i) % 8); - if( v.direct_data.mask[i/8] & (1 << (i %8)) ) - this->direct_data.mask[ (ofs+i) / 8 ] |= dbit; - else - this->direct_data.mask[ (ofs+i) / 8 ] &= ~dbit; + this->set_reloc(ofs + i, POINTER_SIZE, r); } } } } void Value::write_ptr(size_t ofs, size_t ptr_ofs, RelocationPtr reloc) { - if( !this->allocation ) - { - LOG_ERROR("Writing a pointer with no allocation"); - } - this->allocation->write_ptr(ofs, ptr_ofs, ::std::move(reloc)); -} - -::std::ostream& operator<<(::std::ostream& os, const Value& v) -{ - if( v.allocation ) + if( m_inner.is_alloc ) { - os << *v.allocation; + m_inner.alloc.alloc->write_ptr(ofs, ptr_ofs, ::std::move(reloc)); } else { - auto flags = os.flags(); - os << ::std::hex; - for(size_t i = 0; i < v.direct_data.size; i++) + write_usize(ofs, ptr_ofs); + if( ofs != 0 ) { - if( i != 0 ) - os << " "; - if( v.direct_data.mask[i/8] & (1 << i%8) ) - { - os << ::std::setw(2) << ::std::setfill('0') << (int)v.direct_data.data[i]; - } - else - { - os << "--"; - } + LOG_ERROR("Writing a pointer with no allocation"); } - os.setf(flags); + m_inner.direct.reloc_0 = ::std::move(reloc); } +} + +::std::ostream& operator<<(::std::ostream& os, const Value& v) +{ + os << ValueRef(const_cast<Value&>(v), 0, v.size()); return os; } extern ::std::ostream& operator<<(::std::ostream& os, const ValueRef& v) @@ -830,6 +849,8 @@ extern ::std::ostream& operator<<(::std::ostream& os, const ValueRef& v) case RelocationPtr::Ty::Allocation: { const auto& alloc = alloc_ptr.alloc(); + os << &alloc << "@" << v.m_offset << "+" << v.m_size << " "; + auto flags = os.flags(); os << ::std::hex; for(size_t i = v.m_offset; i < ::std::min(alloc.size(), v.m_offset + v.m_size); i++) @@ -837,7 +858,7 @@ extern ::std::ostream& operator<<(::std::ostream& os, const ValueRef& v) if( i != 0 ) os << " "; - if( alloc.mask[i/8] & (1 << i%8) ) + if( alloc.m_mask[i/8] & (1 << i%8) ) { os << ::std::setw(2) << ::std::setfill('0') << (int)alloc.data_ptr()[i]; } @@ -863,9 +884,7 @@ extern ::std::ostream& operator<<(::std::ostream& os, const ValueRef& v) break; case RelocationPtr::Ty::StdString: { const auto& s = alloc_ptr.str(); - assert(v.m_offset < s.size()); - assert(v.m_size < s.size()); - assert(v.m_offset + v.m_size <= s.size()); + assert( in_bounds(v.m_offset, v.m_size, s.size()) ); auto flags = os.flags(); os << ::std::hex; for(size_t i = v.m_offset; i < v.m_offset + v.m_size; i++) @@ -879,9 +898,11 @@ extern ::std::ostream& operator<<(::std::ostream& os, const ValueRef& v) break; } } - else if( v.m_value && v.m_value->allocation ) + else if( v.m_value && v.m_value->m_inner.is_alloc ) { - const auto& alloc = *v.m_value->allocation; + const auto& alloc = *v.m_value->m_inner.alloc.alloc; + + os << &alloc << "@" << v.m_offset << "+" << v.m_size << " "; auto flags = os.flags(); os << ::std::hex; @@ -890,7 +911,7 @@ extern ::std::ostream& operator<<(::std::ostream& os, const ValueRef& v) if( i != 0 ) os << " "; - if( alloc.mask[i/8] & (1 << i%8) ) + if( alloc.m_mask[i/8] & (1 << i%8) ) { os << ::std::setw(2) << ::std::setfill('0') << (int)alloc.data_ptr()[i]; } @@ -913,7 +934,7 @@ extern ::std::ostream& operator<<(::std::ostream& os, const ValueRef& v) } else if( v.m_value ) { - const auto& direct = v.m_value->direct_data; + const auto& direct = v.m_value->m_inner.direct; auto flags = os.flags(); os << ::std::hex; @@ -931,6 +952,10 @@ extern ::std::ostream& operator<<(::std::ostream& os, const ValueRef& v) } } os.setf(flags); + if(direct.reloc_0) + { + os << " { " << direct.reloc_0 << " }"; + } } else { @@ -939,11 +964,28 @@ extern ::std::ostream& operator<<(::std::ostream& os, const ValueRef& v) return os; } +void ValueRef::mark_bytes_valid(size_t ofs, size_t size) +{ + if( m_alloc ) { + switch(m_alloc.get_ty()) + { + case RelocationPtr::Ty::Allocation: + m_alloc.alloc().mark_bytes_valid(m_offset + ofs, size); + break; + default: + LOG_TODO("mark_valid in " << m_alloc); + } + } + else { + m_value->mark_bytes_valid(m_offset + ofs, size); + } +} + Value ValueRef::read_value(size_t ofs, size_t size) const { if( size == 0 ) return Value(); - if( !(ofs < m_size && size <= m_size && ofs + size <= m_size) ) { + if( !in_bounds(ofs, size, m_size) ) { LOG_ERROR("Read exceeds bounds, " << ofs << " + " << size << " > " << m_size << " - from " << *this); } if( m_alloc ) { @@ -952,25 +994,27 @@ Value ValueRef::read_value(size_t ofs, size_t size) const case RelocationPtr::Ty::Allocation: return m_alloc.alloc().read_value(m_offset + ofs, size); case RelocationPtr::Ty::StdString: { + LOG_ASSERT(in_bounds(m_offset + ofs, size, m_alloc.str().size()), ""); auto rv = Value::with_size(size, false); - //ASSERT_BUG(ofs <= m_alloc.str().size(), ""); - //ASSERT_BUG(size <= m_alloc.str().size(), ""); - //ASSERT_BUG(ofs+size <= m_alloc.str().size(), ""); - assert(m_offset+ofs <= m_alloc.str().size() && size <= m_alloc.str().size() && m_offset+ofs+size <= m_alloc.str().size()); rv.write_bytes(0, m_alloc.str().data() + m_offset + ofs, size); return rv; } + case RelocationPtr::Ty::FfiPointer: { + LOG_ASSERT(in_bounds(m_offset + ofs, size, m_alloc.ffi().get_size()), ""); + auto rv = Value::with_size(size, false); + rv.write_bytes(0, reinterpret_cast<const char*>(m_alloc.ffi().ptr_value) + m_offset + ofs, size); + return rv; + } default: - //ASSERT_BUG(m_alloc.is_alloc(), "read_value on non-data backed Value - " << ); - throw "TODO"; + LOG_TODO("read_value from " << m_alloc); } } else { return m_value->read_value(m_offset + ofs, size); } } -bool ValueRef::compare(const void* other, size_t other_len) const +bool ValueRef::compare(size_t offset, const void* other, size_t other_len) const { - check_bytes_valid(0, other_len); - return ::std::memcmp(data_ptr(), other, other_len) == 0; + check_bytes_valid(offset, other_len); + return ::std::memcmp(data_ptr() + offset, other, other_len) == 0; } diff --git a/tools/standalone_miri/value.hpp b/tools/standalone_miri/value.hpp index b057b3c4..03a21970 100644 --- a/tools/standalone_miri/value.hpp +++ b/tools/standalone_miri/value.hpp @@ -13,6 +13,9 @@ #include <cstring> // memcpy #include <cassert> +#include "debug.hpp" +#include "u128.hpp" + namespace HIR { struct TypeRef; struct Path; @@ -21,11 +24,49 @@ class Allocation; struct Value; struct ValueRef; +struct FfiLayout +{ + struct Range { + size_t len; + bool is_valid; + bool is_writable; + }; + ::std::vector<Range> ranges; + + static FfiLayout new_const_bytes(size_t s); + + size_t get_size() const { + size_t rv = 0; + for(const auto& r : ranges) + rv += r.len; + return rv; + } + bool is_valid_read(size_t o, size_t s) const; +}; struct FFIPointer { - const char* source_function; + // FFI pointers require the following: + // - A tag indicating where they're valid/from + // - A data format (e.g. size of allocation, internal data format) + // - If the data format is unspecified (null) then it's a void pointer + // - An actual pointer + + // Pointer value, returned by the FFI void* ptr_value; - size_t size; + // Tag name, used for validty checking by FFI hooks + const char* tag_name; + ::std::shared_ptr<FfiLayout> layout; + + static FFIPointer new_void(const char* name, const void* v) { + return FFIPointer { const_cast<void*>(v), name, ::std::make_shared<FfiLayout>() }; + } + static FFIPointer new_const_bytes(const char* name, const void* s, size_t size) { + return FFIPointer { const_cast<void*>(s), name, ::std::make_shared<FfiLayout>(FfiLayout::new_const_bytes(size)) }; + }; + + size_t get_size() const { + return (layout ? layout->get_size() : 0); + } }; class AllocationHandle @@ -83,7 +124,7 @@ public: RelocationPtr(const RelocationPtr& x); ~RelocationPtr(); static RelocationPtr new_alloc(AllocationHandle h); - static RelocationPtr new_fcn(::HIR::Path p); + static RelocationPtr new_fcn(::HIR::Path p); // TODO: What if it's a FFI function? Could be encoded in here. static RelocationPtr new_string(const ::std::string* s); // NOTE: The string must have a stable pointer static RelocationPtr new_ffi(FFIPointer info); @@ -155,15 +196,20 @@ struct ValueCommonRead uint16_t read_u16(size_t ofs) const { uint16_t rv; read_bytes(ofs, &rv, 2); return rv; } uint32_t read_u32(size_t ofs) const { uint32_t rv; read_bytes(ofs, &rv, 4); return rv; } uint64_t read_u64(size_t ofs) const { uint64_t rv; read_bytes(ofs, &rv, 8); return rv; } + U128 read_u128(size_t ofs) const { U128 rv; read_bytes(ofs, &rv, 16); return rv; } int8_t read_i8(size_t ofs) const { return static_cast<int8_t>(read_u8(ofs)); } int16_t read_i16(size_t ofs) const { return static_cast<int16_t>(read_u16(ofs)); } int32_t read_i32(size_t ofs) const { return static_cast<int32_t>(read_u32(ofs)); } int64_t read_i64(size_t ofs) const { return static_cast<int64_t>(read_u64(ofs)); } + I128 read_i128(size_t ofs) const { I128 rv; read_bytes(ofs, &rv, 16); return rv; } 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)); } + /// De-reference a pointer (of target type `ty`) at the given offset, and return a reference to it + ValueRef deref(size_t ofs, const ::HIR::TypeRef& ty); + /// Read a pointer from the value, requiring at least `req_valid` valid bytes, saves avaliable space in `size` void* read_pointer_unsafe(size_t rd_ofs, size_t req_valid, size_t& size, bool& is_mut) const; /// Read a pointer, requiring `req_len` valid bytes @@ -177,8 +223,7 @@ struct ValueCommonRead bool is_mut; void* rv = read_pointer_unsafe(rd_ofs, 0, out_size, is_mut); if(!is_mut) - throw ""; - //LOG_FATAL("Attempting to get an uninit pointer to immutable data"); + LOG_FATAL("Attempting to get an uninit pointer to immutable data"); return rv; } /// Read a pointer and return a ValueRef to it (mutable data) @@ -193,10 +238,12 @@ struct ValueCommonWrite: 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_u128(size_t ofs, U128 v) { write_bytes(ofs, &v, 16); } 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_i128(size_t ofs, I128 v) { write_bytes(ofs, &v, 16); } void write_f32(size_t ofs, float v) { write_bytes(ofs, &v, 4); } void write_f64(size_t ofs, double v) { write_bytes(ofs, &v, 8); } void write_usize(size_t ofs, uint64_t v); @@ -208,19 +255,31 @@ class Allocation: public ValueCommonWrite { friend class AllocationHandle; + + static uint64_t s_next_index; + + ::std::string m_tag; size_t refcount; + size_t m_size; + uint64_t m_index; // TODO: Read-only flag? bool is_freed = false; + + ::std::vector<uint64_t> m_data; +public: + ::std::vector<uint8_t> m_mask; + ::std::vector<Relocation> relocations; public: - static AllocationHandle new_alloc(size_t size); + virtual ~Allocation() {} + static AllocationHandle new_alloc(size_t size, ::std::string tag); - const uint8_t* data_ptr() const { return reinterpret_cast<const uint8_t*>(this->data.data()); } - uint8_t* data_ptr() { return reinterpret_cast< uint8_t*>(this->data.data()); } - size_t size() const { return this->data.size() * 8; } + // NOTE: This should match the value in the MMIR backend + static const size_t PTR_BASE = 0x1000; - ::std::vector<uint64_t> data; - ::std::vector<uint8_t> mask; - ::std::vector<Relocation> relocations; + const uint8_t* data_ptr() const { return reinterpret_cast<const uint8_t*>(this->m_data.data()); } + uint8_t* data_ptr() { return reinterpret_cast< uint8_t*>(this->m_data.data()); } + size_t size() const { return m_size; } + const ::std::string& tag() const { return m_tag; } RelocationPtr get_relocation(size_t ofs) const override { for(const auto& r : relocations) { @@ -232,7 +291,7 @@ public: void mark_as_freed() { is_freed = true; relocations.clear(); - for(auto& v : mask) + for(auto& v : m_mask) v = 0; } @@ -247,23 +306,95 @@ public: void write_value(size_t ofs, Value v); void write_bytes(size_t ofs, const void* src, size_t count) override; void write_ptr(size_t ofs, size_t ptr_ofs, RelocationPtr reloc) override; + + void set_reloc(size_t ofs, size_t len, RelocationPtr reloc); + friend ::std::ostream& operator<<(::std::ostream& os, const Allocation* x); }; extern ::std::ostream& operator<<(::std::ostream& os, const Allocation& x); +// TODO: Rename to be `StackSlot` struct Value: public ValueCommonWrite { - // If NULL, data is direct - AllocationHandle allocation; - struct { - // NOTE: Can't pack the mask+size tighter, need 4 bits of size (8-15) leaving 12 bits of mask - uint8_t data[2*8-3]; // 13 data bytes, plus 16bit mask, plus size = 16 bytes - uint8_t mask[2]; - uint8_t size; - } direct_data; +//private: + union Inner { + bool is_alloc; + struct Alloc { + bool is_alloc; + AllocationHandle alloc; + + Alloc(AllocationHandle alloc): + is_alloc(true), + alloc(::std::move(alloc)) + { + } + } alloc; + // Sizeof = 4+8+8 = 20b (plus vtable?) + struct Direct { + bool is_alloc; + uint8_t size; + uint8_t mask[2]; + uint8_t data[2*8]; + RelocationPtr reloc_0; // Relocation only for 0+POINTER_SIZE + + Direct(size_t size): + is_alloc(false), + size(static_cast<uint8_t>(size)), + mask{0,0} + { + } + } direct; + + Inner(): + direct(0) + { + } + ~Inner() { + if(is_alloc) { + alloc.~Alloc(); + } + else { + direct.~Direct(); + } + } + Inner(const Inner& x) { + if(x.is_alloc) { + new(&this->alloc) Alloc(x.alloc); + } + else { + new(&this->direct) Direct(x.direct); + } + } + Inner(Inner&& x) { + if(x.is_alloc) { + new(&this->alloc) Alloc(::std::move(x.alloc)); + } + else { + new(&this->direct) Direct(::std::move(x.direct)); + } + } + Inner& operator=(Inner&& x) { + this->~Inner(); + new(this) Inner(x); + return *this; + } + Inner& operator=(const Inner& x) { + this->~Inner(); + new(this) Inner(x); + return *this; + } + } m_inner; +public: Value(); Value(::HIR::TypeRef ty); + ~Value() { + } + + Value(const Value&) = default; + Value(Value&&) = default; + Value& operator=(const Value& x) = delete; + Value& operator=(Value&& x) = default; static Value with_size(size_t size, bool have_allocation); static Value new_fnptr(const ::HIR::Path& fn_path); @@ -273,17 +404,51 @@ struct Value: static Value new_isize(int64_t v); static Value new_u32(uint32_t v); static Value new_i32(int32_t v); + static Value new_i64(int64_t v); + AllocationHandle borrow(::std::string loc) { + if( !m_inner.is_alloc ) + create_allocation(/*loc*/); + return m_inner.alloc.alloc; + } + void ensure_allocation() { + if( !m_inner.is_alloc ) + create_allocation(); + } void create_allocation(); - size_t size() const { return allocation ? allocation->size() : direct_data.size; } - const uint8_t* data_ptr() const { return allocation ? allocation->data_ptr() : direct_data.data; } - uint8_t* data_ptr() { return allocation ? allocation->data_ptr() : direct_data.data; } + size_t size() const { return m_inner.is_alloc ? m_inner.alloc.alloc->size() : m_inner.direct.size; } + const uint8_t* data_ptr() const { return m_inner.is_alloc ? m_inner.alloc.alloc->data_ptr() : m_inner.direct.data; } + uint8_t* data_ptr() { return m_inner.is_alloc ? m_inner.alloc.alloc->data_ptr() : m_inner.direct.data; } + const uint8_t* get_mask() const { return m_inner.is_alloc ? m_inner.alloc.alloc->m_mask.data() : m_inner.direct.mask; } + uint8_t* get_mask_mut() { return m_inner.is_alloc ? m_inner.alloc.alloc->m_mask.data() : m_inner.direct.mask; } RelocationPtr get_relocation(size_t ofs) const override { - if( this->allocation && this->allocation ) - return this->allocation->get_relocation(ofs); + if( m_inner.is_alloc ) + return m_inner.alloc.alloc->get_relocation(ofs); + else if(ofs == 0) + { + return m_inner.direct.reloc_0; + } else + { return RelocationPtr(); + } + } + void set_reloc(size_t ofs, size_t size, RelocationPtr p) /*override*/ { + if( m_inner.is_alloc ) + { + m_inner.alloc.alloc->set_reloc(ofs, size, ::std::move(p)); + } + else if( ofs == 0 /*&& size == POINTER_SIZE*/ ) + { + m_inner.direct.reloc_0 = ::std::move(p); + } + else + { + this->create_allocation(); + assert( m_inner.is_alloc ); + m_inner.alloc.alloc->set_reloc(ofs, size, ::std::move(p)); + } } void check_bytes_valid(size_t ofs, size_t size) const; @@ -310,6 +475,17 @@ struct ValueRef: size_t m_size; // Size in bytes of the referenced value ::std::shared_ptr<Value> m_metadata; + static bool in_bounds(size_t ofs, size_t size, size_t max_size) { + if( size == 0 ) { + return ofs <= max_size; + } + if( ofs > 0 && !(ofs < max_size) ) + return false; + if( !(size <= max_size) ) + return false; + return ofs + size <= max_size; + } + ValueRef(RelocationPtr ptr, size_t ofs, size_t size): m_alloc(ptr), m_value(nullptr), @@ -321,22 +497,25 @@ struct ValueRef: switch(m_alloc.get_ty()) { case RelocationPtr::Ty::Allocation: - assert(ofs < m_alloc.alloc().size()); - assert(size <= m_alloc.alloc().size()); - assert(ofs+size <= m_alloc.alloc().size()); + if( !in_bounds(ofs, size, m_alloc.alloc().size()) ) + { + LOG_NOTICE("ValueRef exceeds bounds of " << m_alloc << " - " << ofs << "+" << size << " > " << m_alloc.alloc().size()); + } break; case RelocationPtr::Ty::StdString: - assert(ofs < m_alloc.str().size()); - assert(size <= m_alloc.str().size()); - assert(ofs+size <= m_alloc.str().size()); + if( !in_bounds(ofs, size, m_alloc.str().size()) ) + { + LOG_NOTICE("ValueRef exceeds bounds of string - " << ofs << "+" << size << " > " << m_alloc.str().size()); + } break; case RelocationPtr::Ty::FfiPointer: - assert(ofs < m_alloc.ffi().size); - assert(size <= m_alloc.ffi().size); - assert(ofs+size <= m_alloc.ffi().size); + if( !in_bounds(ofs, size, m_alloc.ffi().get_size()) ) + { + LOG_NOTICE("ValueRef exceeds bounds of FFI buffer - " << ofs << "+" << size << " > " << m_alloc.ffi().get_size()); + } break; - default: - throw "TODO"; + case RelocationPtr::Ty::Function: + LOG_TODO(""); } } } @@ -355,13 +534,13 @@ struct ValueRef: if(m_alloc) { if( m_alloc.is_alloc() ) - return m_alloc.alloc().get_relocation(ofs); + return m_alloc.alloc().get_relocation(m_offset + ofs); else return RelocationPtr(); } else if( m_value ) { - return m_value->get_relocation(ofs); + return m_value->get_relocation(m_offset + ofs); } else { @@ -379,7 +558,27 @@ struct ValueRef: case RelocationPtr::Ty::StdString: return reinterpret_cast<const uint8_t*>(m_alloc.str().data() + m_offset); default: - throw "TODO"; + LOG_TODO(""); + } + } + else if( m_value ) { + return m_value->data_ptr() + m_offset; + } + else { + return nullptr; + } + } + + // TODO: Remove these two (move to a helper?) + uint8_t* data_ptr_mut() { + if( m_alloc ) { + switch(m_alloc.get_ty()) + { + case RelocationPtr::Ty::Allocation: + return m_alloc.alloc().data_ptr() + m_offset; + break; + default: + LOG_TODO(""); } } else if( m_value ) { @@ -389,12 +588,12 @@ struct ValueRef: return nullptr; } } + void mark_bytes_valid(size_t ofs, size_t size); + void read_bytes(size_t ofs, void* dst, size_t size) const { if( size == 0 ) return ; - assert(ofs < m_size); - assert(size <= m_size); - assert(ofs+size <= m_size); + LOG_ASSERT(in_bounds(ofs, size, m_size), "read_bytes(" << ofs << "+" << size << " > " << m_size <<")"); if( m_alloc ) { switch(m_alloc.get_ty()) { @@ -407,7 +606,7 @@ struct ValueRef: break; default: //ASSERT_BUG(m_alloc.is_alloc(), "read_value on non-data backed Value - " << ); - throw "TODO"; + LOG_TODO(""); } } else { @@ -417,9 +616,7 @@ struct ValueRef: void check_bytes_valid(size_t ofs, size_t size) const { if( size == 0 ) return ; - assert(ofs < m_size); - assert(size <= m_size); - assert(ofs+size <= m_size); + LOG_ASSERT(in_bounds(ofs, size, m_size), "check_bytes_valid(" << ofs << "+" << size << " > " << m_size <<")"); if( m_alloc ) { switch(m_alloc.get_ty()) { @@ -431,7 +628,7 @@ struct ValueRef: break; default: //ASSERT_BUG(m_alloc.is_alloc(), "read_value on non-data backed Value - " << ); - throw "TODO"; + LOG_TODO(""); } } else { @@ -439,6 +636,10 @@ struct ValueRef: } } - bool compare(const void* other, size_t other_len) const; + bool compare(size_t offset, const void* other, size_t other_len) const; }; extern ::std::ostream& operator<<(::std::ostream& os, const ValueRef& v); +//struct ValueRefMut: +// public ValueCommonWrite +//{ +//}; diff --git a/tools/standalone_miri/win32.api b/tools/standalone_miri/win32.api new file mode 100644 index 00000000..1f374c1e --- /dev/null +++ b/tools/standalone_miri/win32.api @@ -0,0 +1,24 @@ +# +# Windows API calls +# + +type HMODULE = void [size(0), name("HMODULE")]; + +#fn GetModuleHandleW(lpcwsName: *const [cstr,null] u16) -> *const [null] HMODULE = "Kernel32.dll":"GetModuleHandleW"; +fn GetModuleHandleW(lpcwsName: *const [cstr,null] u16) -> *const [null] HMODULE { + miri::ensure_valid_nulseq("lpcwsName", lpcwsName); + return miri::call_ptr("Kernel32.dll", "GetModuleHandleW", lpcwsName); +} + +# - The returned function pointer is annotated with the passed name +fn GetProcAddress(hModule: *const HMODULE, name: *const [cstr] u8) -> fn(?) [null] { + miri::ensure_valid_nulseq("name", name); + let rv = miri::call_ptr("Kernel32.dll", "GetProcAddress", hModule, name); + # Create a named function pointer from the raw pointer return, that will go though the .api file + return miri::make_named_fn_ptr(rv, miri::cstr8_to_string(name)); +} + +fn AddVectoredExceptionHandler(..) -> usize { + 1 +} + diff --git a/tools/testrunner/main.cpp b/tools/testrunner/main.cpp index 3823215a..a634d87a 100644 --- a/tools/testrunner/main.cpp +++ b/tools/testrunner/main.cpp @@ -37,6 +37,9 @@ struct Options const char* input_glob = nullptr; ::std::vector<::std::string> test_list; + bool debug_enabled; + ::std::vector<::std::string> lib_dirs; + int debug_level = 0; const char* exceptions_file = nullptr; @@ -55,6 +58,12 @@ struct TestDesc ::std::vector<::std::string> m_pre_build; ::std::vector<::std::string> m_extra_flags; bool ignore; + bool no_run; + TestDesc() + :ignore(false) + ,no_run(false) + { + } }; struct Timestamp { @@ -94,20 +103,27 @@ struct Timestamp } }; -bool run_executable(const ::helpers::path& file, const ::std::vector<const char*>& args, const ::helpers::path& outfile); +bool run_executable(const ::helpers::path& file, const ::std::vector<const char*>& args, const ::helpers::path& outfile, unsigned timeout_seconds); -bool run_compiler(const ::helpers::path& source_file, const ::helpers::path& output, const ::std::vector<::std::string>& extra_flags, ::helpers::path libdir={}, bool is_dep=false) +bool run_compiler(const Options& opts, const ::helpers::path& source_file, const ::helpers::path& output, const ::std::vector<::std::string>& extra_flags, ::helpers::path libdir={}, bool is_dep=false) { ::std::vector<const char*> args; args.push_back("mrustc"); // Force optimised and debuggable args.push_back("-O"); - // TODO: Only turn debug on when requested by the caller - //args.push_back("-g"); - args.push_back("-L"); - args.push_back("output"); + // Only turn debug on when requested by the caller + if( opts.debug_enabled ) + { + args.push_back("-g"); + } + + for(const auto& d : opts.lib_dirs) + { + args.push_back("-L"); + args.push_back(d.c_str()); + } if(libdir.is_valid()) { args.push_back("-L"); @@ -134,7 +150,16 @@ bool run_compiler(const ::helpers::path& source_file, const ::helpers::path& out for(const auto& s : extra_flags) args.push_back(s.c_str()); - return run_executable(MRUSTC_PATH, args, logfile); + return run_executable(MRUSTC_PATH, args, logfile, 0); +} + +static bool gTimeout = false; +static bool gInterrupted = false; +void sigalrm_handler(int) { + gTimeout = true; +} +void sigint_handler(int) { + gInterrupted = true; } int main(int argc, const char* argv[]) @@ -145,6 +170,16 @@ int main(int argc, const char* argv[]) return v; } +#ifdef _WIN32 +#else + { + struct sigaction sa = {0}; + sa.sa_handler = sigalrm_handler; + sigaction(SIGALRM, &sa, NULL); + signal(SIGINT, sigint_handler); + } +#endif + ::std::vector<::std::string> skip_list; // > Filter out tests listed in an exceptions file (newline separated, supports comments) if( opts.exceptions_file ) @@ -247,6 +282,10 @@ int main(int argc, const char* argv[]) { td.ignore = true; } + else if( line.substr(start, 4+1+7) == "skip-codegen" ) + { + td.no_run = true; + } else if( line.substr(start, 14) == "compile-flags:" ) { auto end = line.find(' ', 3+14); @@ -290,13 +329,19 @@ int main(int argc, const char* argv[]) ::std::sort(tests.begin(), tests.end(), [](const auto& a, const auto& b){ return a.m_name < b.m_name; }); // --- - auto compiler_ts = getenv("TESTRUNNER_NOCOMPILERDEP") ? Timestamp::infinite_past() : Timestamp::for_file(MRUSTC_PATH); + const bool SKIP_PASS = (getenv("TESTRUNNER_SKIPPASS") != nullptr); + const bool NO_COMPILER_DEP = (getenv("TESTRUNNER_NOCOMPILERDEP") != nullptr); + const auto compiler_ts = Timestamp::for_file(MRUSTC_PATH); unsigned n_skip = 0; unsigned n_cfail = 0; unsigned n_fail = 0; unsigned n_ok = 0; for(const auto& test : tests) { + if( gInterrupted ) { + DEBUG(">> Interrupted"); + return 1; + } if( !opts.test_list.empty() && ::std::find(opts.test_list.begin(), opts.test_list.end(), test.m_name) == opts.test_list.end() ) { if( opts.debug_level > 0 ) @@ -319,10 +364,23 @@ int main(int argc, const char* argv[]) //DEBUG(">> " << test.m_name); auto depdir = outdir / "deps-" + test.m_name.c_str(); - auto outfile = outdir / test.m_name + ".exe"; - - auto test_output_ts = Timestamp::for_file(outfile); - if( test_output_ts == Timestamp::infinite_past() || test_output_ts < compiler_ts ) + auto test_exe = outdir / test.m_name + ".exe"; + auto test_output = outdir / test.m_name + ".out"; + + auto test_exe_ts = Timestamp::for_file(test_exe); + auto test_output_ts = Timestamp::for_file(test_output); + // (Optional) if the target file doesn't exist, force a re-compile IF the compiler is newer than the + // executable. + if( SKIP_PASS ) + { + // If output is missing (the last run didn't succeed), and the compiler is newer than the executable + if( test_output_ts == Timestamp::infinite_past() && test_exe_ts < compiler_ts ) + { + // Force a recompile + test_exe_ts = Timestamp::infinite_past(); + } + } + if( test_exe_ts == Timestamp::infinite_past() || (!NO_COMPILER_DEP && !SKIP_PASS && test_exe_ts < compiler_ts) ) { bool pre_build_failed = false; for(const auto& file : test.m_pre_build) @@ -333,7 +391,7 @@ int main(int argc, const char* argv[]) mkdir(depdir.str().c_str(), 0755); #endif auto infile = input_path / "auxiliary" / file; - if( !run_compiler(infile, depdir, {}, depdir, true) ) + if( !run_compiler(opts, infile, depdir, {}, depdir, true) ) { DEBUG("COMPILE FAIL " << infile << " (dep of " << test.m_name << ")"); n_cfail ++; @@ -355,30 +413,36 @@ int main(int argc, const char* argv[]) depdir = ::helpers::path(); } - auto compile_logfile = outdir / test.m_name + "-build.log"; - if( !run_compiler(test.m_path, outfile, test.m_extra_flags, depdir) ) + auto compile_logfile = test_exe + "-build.log"; + if( !run_compiler(opts, test.m_path, test_exe, test.m_extra_flags, depdir) ) { - DEBUG("COMPILE FAIL " << test.m_name); + DEBUG("COMPILE FAIL " << test.m_name << ", log in " << compile_logfile); n_cfail ++; if( opts.fail_fast ) return 1; else continue; } - test_output_ts = Timestamp::for_file(outfile); + test_exe_ts = Timestamp::for_file(test_exe); } // - Run the test - auto run_out_file = outdir / test.m_name + ".out"; - if( Timestamp::for_file(run_out_file) < test_output_ts ) + if( test.no_run ) { - if( !run_executable(outfile, { outfile.str().c_str() }, run_out_file) ) + ::std::ofstream(test_output.str()) << ""; + if( opts.debug_level > 0 ) + DEBUG("No run " << test.m_name); + } + else if( test_output_ts < test_exe_ts ) + { + auto run_out_file_tmp = test_output + ".tmp"; + if( !run_executable(test_exe, { test_exe.str().c_str() }, run_out_file_tmp, 10) ) { DEBUG("RUN FAIL " << test.m_name); // Move the failing output file - auto fail_file = run_out_file + "_failed"; + auto fail_file = test_output + "_failed"; remove(fail_file.str().c_str()); - rename(run_out_file.str().c_str(), fail_file.str().c_str()); + rename(run_out_file_tmp.str().c_str(), fail_file.str().c_str()); DEBUG("- Output in " << fail_file); n_fail ++; @@ -387,6 +451,11 @@ int main(int argc, const char* argv[]) else continue; } + else + { + remove(test_output.str().c_str()); + rename(run_out_file_tmp.str().c_str(), test_output.str().c_str()); + } } else { @@ -442,6 +511,16 @@ int Options::parse(int argc, const char* argv[]) case 'v': this->debug_level += 1; break; + case 'g': + this->debug_enabled = true; + break; + case 'L': + if( i+1 == argc ) { + this->usage_short(); + return 1; + } + this->lib_dirs.push_back( argv[++i] ); + break; default: this->usage_short(); @@ -501,7 +580,7 @@ void Options::usage_full() const } /// -bool run_executable(const ::helpers::path& exe_name, const ::std::vector<const char*>& args, const ::helpers::path& outfile) +bool run_executable(const ::helpers::path& exe_name, const ::std::vector<const char*>& args, const ::helpers::path& outfile, unsigned timeout_seconds) { #ifdef _WIN32 ::std::stringstream cmdline; @@ -530,6 +609,7 @@ bool run_executable(const ::helpers::path& exe_name, const ::std::vector<const c CreateProcessA(exe_name.str().c_str(), (LPSTR)cmdline_str.c_str(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); SetErrorMode(em); CloseHandle(si.hStdOutput); + // TODO: Use timeout_seconds WaitForSingleObject(pi.hProcess, INFINITE); DWORD status = 1; GetExitCodeProcess(pi.hProcess, &status); @@ -567,15 +647,24 @@ bool run_executable(const ::helpers::path& exe_name, const ::std::vector<const c posix_spawn_file_actions_destroy(&file_actions); int status = -1; - waitpid(pid, &status, 0); + // NOTE: `alarm(0)` clears any pending alarm, so no need to check before + // calling + alarm(timeout_seconds); + if( waitpid(pid, &status, 0) <= 0 ) + { + DEBUG(exe_name << " timed out, killing it"); + kill(pid, SIGKILL); + return false; + } + alarm(0); if( status != 0 ) { if( WIFEXITED(status) ) - DEBUG(exe_name << " exited with non-zero exit status " << WEXITSTATUS(status) << ", see log " << outfile_str); + DEBUG(exe_name << " exited with non-zero exit status " << WEXITSTATUS(status)); else if( WIFSIGNALED(status) ) - DEBUG(exe_name << " was terminated with signal " << WTERMSIG(status) << ", see log " << outfile_str); + DEBUG(exe_name << " was terminated with signal " << WTERMSIG(status)); else - DEBUG(exe_name << " terminated for unknown reason, status=" << status << ", see log " << outfile_str); + DEBUG(exe_name << " terminated for unknown reason, status=" << status); return false; } #endif |