From eed40bca6f2eb3af0c811cf6ec9e123c5bf4907d Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Mon, 4 Jan 2021 22:13:40 -0800 Subject: [PATCH] compiler: initialize variables with go:embed directives This completes the compiler work for go:embed. Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/281536 --- gcc/go/gofrontend/MERGE | 2 +- gcc/go/gofrontend/embed.cc | 322 ++++++++++++++++++++++++++++++++++++- gcc/go/gofrontend/gogo.cc | 11 ++ gcc/go/gofrontend/gogo.h | 14 ++ 4 files changed, 347 insertions(+), 2 deletions(-) diff --git a/gcc/go/gofrontend/MERGE b/gcc/go/gofrontend/MERGE index fb4ec30913e..f67c30a5d3a 100644 --- a/gcc/go/gofrontend/MERGE +++ b/gcc/go/gofrontend/MERGE @@ -1,4 +1,4 @@ -9e78cef2b689aa586dbf677fb47ea3f08f197b91 +83eea1930671ce2bba863582a67f2609bc4f9f36 The first line of this file holds the git revision number of the last merge done from the gofrontend repository. diff --git a/gcc/go/gofrontend/embed.cc b/gcc/go/gofrontend/embed.cc index 7ee86746212..bea1003bc08 100644 --- a/gcc/go/gofrontend/embed.cc +++ b/gcc/go/gofrontend/embed.cc @@ -9,6 +9,8 @@ #include "operator.h" #include "go-diagnostics.h" #include "lex.h" +#include "types.h" +#include "expressions.h" #include "gogo.h" #ifndef O_BINARY @@ -301,7 +303,41 @@ Gogo::read_embedcfg(const char *filename) return; } - // TODO: Actually do something with patterns and files. + for (Json_value::map_iterator p = patterns->map_begin(); + p != patterns->map_end(); + ++p) + { + if (p->second->classification() != Json_value::JSON_VALUE_ARRAY) + { + r.error("invalid embedcfg: Patterns entry is not an array"); + return; + } + std::vector files; + p->second->get_and_clear_array(&files); + + std::pair > val; + val.first = p->first; + std::pair ins = + this->embed_patterns_.insert(val); + if (!ins.second) + { + r.error("invalid embedcfg: duplicate Patterns entry"); + return; + } + std::swap(ins.first->second, files); + } + + for (Json_value::map_iterator p = files->map_begin(); + p != files->map_end(); + ++p) + { + if (p->second->classification() != Json_value::JSON_VALUE_STRING) + { + r.error("invalid embedcfg: Files entry is not a string"); + return; + } + this->embed_files_[p->first] = p->second->to_string(); + } } // Read the contents of FILENAME into this->data_. Returns whether it @@ -641,3 +677,287 @@ Gogo::is_embed_imported() const // the package has been imported if there is at least one alias. return !p->second->aliases().empty(); } + +// Implement the sort order for a list of embedded files, as discussed +// at the docs for embed.FS. + +class Embedfs_sort +{ + public: + bool + operator()(const std::string& p1, const std::string& p2) const; + + private: + void + split(const std::string&, size_t*, size_t*, size_t*) const; +}; + +bool +Embedfs_sort::operator()(const std::string& p1, const std::string& p2) const +{ + size_t dirlen1, elem1, elemlen1; + this->split(p1, &dirlen1, &elem1, &elemlen1); + size_t dirlen2, elem2, elemlen2; + this->split(p2, &dirlen2, &elem2, &elemlen2); + + if (dirlen1 == 0) + { + if (dirlen2 > 0) + { + int i = p2.compare(0, dirlen2, "."); + if (i != 0) + return i > 0; + } + } + else if (dirlen2 == 0) + { + int i = p1.compare(0, dirlen1, "."); + if (i != 0) + return i < 0; + } + else + { + int i = p1.compare(0, dirlen1, p2, 0, dirlen2); + if (i != 0) + return i < 0; + } + + int i = p1.compare(elem1, elemlen1, p2, elem2, elemlen2); + return i < 0; +} + +// Pick out the directory and file name components for comparison. + +void +Embedfs_sort::split(const std::string& s, size_t* dirlen, size_t* elem, + size_t* elemlen) const +{ + size_t len = s.size(); + if (len > 0 && s[len - 1] == '/') + --len; + size_t slash = s.rfind('/', len - 1); + if (slash == std::string::npos) + { + *dirlen = 0; + *elem = 0; + *elemlen = len; + } + else + { + *dirlen = slash; + *elem = slash + 1; + *elemlen = len - (slash + 1); + } +} + +// Convert the go:embed directives for a variable into an initializer +// for that variable. + +Expression* +Gogo::initializer_for_embeds(Type* type, + const std::vector* embeds, + Location loc) +{ + if (this->embed_patterns_.empty()) + { + go_error_at(loc, + ("invalid go:embed: build system did not " + "supply embed configuration")); + return Expression::make_error(loc); + } + + type = type->unalias(); + + enum { + EMBED_STRING = 0, + EMBED_BYTES = 1, + EMBED_FS = 2 + } embed_kind; + + const Named_type* nt = type->named_type(); + if (nt != NULL + && nt->named_object()->package() != NULL + && nt->named_object()->package()->pkgpath() == "embed" + && nt->name() == "FS") + embed_kind = EMBED_FS; + else if (type->is_string_type()) + embed_kind = EMBED_STRING; + else if (type->is_slice_type() + && type->array_type()->element_type()->integer_type() != NULL + && type->array_type()->element_type()->integer_type()->is_byte()) + embed_kind = EMBED_BYTES; + else + { + go_error_at(loc, "invalid type for go:embed"); + return Expression::make_error(loc); + } + + // The patterns in the go:embed directive(s) are in EMBEDS. Find + // them in the patterns in the embedcfg file. + + Unordered_set(std::string) have; + std::vector paths; + for (std::vector::const_iterator pe = embeds->begin(); + pe != embeds->end(); + pe++) + { + Embed_patterns::const_iterator pp = this->embed_patterns_.find(*pe); + if (pp == this->embed_patterns_.end()) + { + go_error_at(loc, + ("invalid go:embed: build system did not " + "map pattern %<%s%>"), + pe->c_str()); + continue; + } + + // Each pattern in the embedcfg file maps to a list of file + // names. For each file name, the embedcfg file records an + // absolute path. Add those absolute paths to PATHS. + for (std::vector::const_iterator pf = pp->second.begin(); + pf != pp->second.end(); + pf++) + { + if (this->embed_files_.find(*pf) == this->embed_files_.end()) + { + go_error_at(loc, + ("invalid go:embed: build system did not " + "map file %<%s%>"), + pf->c_str()); + continue; + } + + std::pair ins + = have.insert(*pf); + if (ins.second) + { + const std::string& path(*pf); + paths.push_back(path); + + if (embed_kind == EMBED_FS) + { + // Add each required directory, with a trailing slash. + size_t i = std::string::npos; + while (i > 0) + { + i = path.rfind('/', i); + if (i == std::string::npos) + break; + std::string dir = path.substr(0, i + 1); + ins = have.insert(dir); + if (ins.second) + paths.push_back(dir); + --i; + } + } + } + } + } + + if (embed_kind == EMBED_STRING || embed_kind == EMBED_BYTES) + { + if (paths.size() > 1) + { + go_error_at(loc, + ("invalid go:embed: multiple files for " + "string or byte slice"));; + return Expression::make_error(loc); + } + + std::string data; + if (!read_file(paths[0].c_str(), loc, &data)) + return Expression::make_error(loc); + + Expression* e = Expression::make_string(data, loc); + if (embed_kind == EMBED_BYTES) + e = Expression::make_cast(type, e, loc); + return e; + } + + std::sort(paths.begin(), paths.end(), Embedfs_sort()); + + if (type->struct_type() == NULL + || type->struct_type()->field_count() != 1) + { + go_error_at(loc, + ("internal error: embed.FS should be struct type " + "with one field")); + return Expression::make_error(loc); + } + + Type* ptr_type = type->struct_type()->field(0)->type(); + if (ptr_type->points_to() == NULL) + { + go_error_at(loc, + "internal error: embed.FS struct field should be pointer"); + return Expression::make_error(loc); + } + + Type* slice_type = ptr_type->points_to(); + if (!slice_type->is_slice_type()) + { + go_error_at(loc, + ("internal error: embed.FS struct field should be " + "pointer to slice")); + return Expression::make_error(loc); + } + + Type* file_type = slice_type->array_type()->element_type(); + if (file_type->struct_type() == NULL + || (file_type->struct_type()->find_local_field(".embed.name", NULL) + == NULL) + || (file_type->struct_type()->find_local_field(".embed.data", NULL) + == NULL)) + { + go_error_at(loc, + ("internal error: embed.FS slice element should be struct " + "with name and data fields")); + return Expression::make_error(loc); + } + + const Struct_field_list* file_fields = file_type->struct_type()->fields(); + Expression_list* file_vals = new(Expression_list); + file_vals->reserve(paths.size()); + for (std::vector::const_iterator pp = paths.begin(); + pp != paths.end(); + ++pp) + { + std::string data; + if ((*pp)[pp->size() - 1] != '/') + { + if (!read_file(this->embed_files_[*pp].c_str(), loc, &data)) + return Expression::make_error(loc); + } + + Expression_list* field_vals = new(Expression_list); + for (Struct_field_list::const_iterator pf = file_fields->begin(); + pf != file_fields->end(); + ++pf) + { + if (pf->is_field_name(".embed.name")) + field_vals->push_back(Expression::make_string(*pp, loc)); + else if (pf->is_field_name(".embed.data")) + field_vals->push_back(Expression::make_string(data, loc)); + else + { + // FIXME: The embed.file type has a hash field, which is + // currently unused. We should fill it in, but don't. + // The hash is a SHA256, and we don't have convenient + // SHA256 code. Do this later when the field is + // actually used. + field_vals->push_back(NULL); + } + } + + Expression* file_val = + Expression::make_struct_composite_literal(file_type, field_vals, loc); + file_vals->push_back(file_val); + } + + Expression* slice_init = + Expression::make_slice_composite_literal(slice_type, file_vals, loc); + Expression* fs_init = Expression::make_heap_expression(slice_init, loc); + Expression_list* fs_vals = new Expression_list(); + fs_vals->push_back(fs_init); + return Expression::make_struct_composite_literal(type, fs_vals, loc); +} diff --git a/gcc/go/gofrontend/gogo.cc b/gcc/go/gofrontend/gogo.cc index 4c795a2b495..62b06be827b 100644 --- a/gcc/go/gofrontend/gogo.cc +++ b/gcc/go/gofrontend/gogo.cc @@ -7502,6 +7502,17 @@ Variable::lower_init_expression(Gogo* gogo, Named_object* function, if (dep != NULL && dep->is_variable()) dep->var_value()->lower_init_expression(gogo, function, inserter); + if (this->embeds_ != NULL) + { + // Now that we have seen any possible type aliases, convert the + // go:embed directives into an initializer. + go_assert(this->init_ == NULL && this->type_ != NULL); + this->init_ = gogo->initializer_for_embeds(this->type_, this->embeds_, + this->location_); + delete this->embeds_; + this->embeds_ = NULL; + } + if (this->init_ != NULL && !this->init_is_lowered_) { if (this->seen_) diff --git a/gcc/go/gofrontend/gogo.h b/gcc/go/gofrontend/gogo.h index 891ef697ffe..51b6575ba47 100644 --- a/gcc/go/gofrontend/gogo.h +++ b/gcc/go/gofrontend/gogo.h @@ -401,6 +401,10 @@ class Gogo bool is_embed_imported() const; + // Build an initializer for a variable with a go:embed directive. + Expression* + initializer_for_embeds(Type*, const std::vector*, Location); + // Return whether to check for division by zero in binary operations. bool check_divide_by_zero() const @@ -1178,6 +1182,12 @@ class Gogo static bool is_digits(const std::string&); + // Type used to map go:embed patterns to a list of files. + typedef Unordered_map(std::string, std::vector) Embed_patterns; + + // Type used to map go:embed file names to their full path. + typedef Unordered_map(std::string, std::string) Embed_files; + // Type used to map import names to packages. typedef std::map Imports; @@ -1273,6 +1283,10 @@ class Gogo std::string relative_import_path_; // The C header file to write, from the -fgo-c-header option. std::string c_header_; + // Patterns from an embedcfg file. + Embed_patterns embed_patterns_; + // Mapping from file to full path from an embedcfg file. + Embed_files embed_files_; // Whether or not to check for division by zero, from the // -fgo-check-divide-zero option. bool check_divide_by_zero_; -- 2.30.2