From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp-out1.suse.de (smtp-out1.suse.de [IPv6:2a07:de40:b251:101:10:150:64:1]) by sourceware.org (Postfix) with ESMTPS id CB5A23891C0D for ; Thu, 20 Jun 2024 11:31:24 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org CB5A23891C0D Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=suse.cz Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=suse.cz ARC-Filter: OpenARC Filter v1.0.0 sourceware.org CB5A23891C0D Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2a07:de40:b251:101:10:150:64:1 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1718883089; cv=none; b=CXI7w1oTkuPPDRxVxHjya1TIvAKCP6QpMqpUC4I5Pw7ouWCCmOrARUl41LUb7bEeyImmojBuQcZY7JBNtAJxq7N3QPegOjJtrrWvdQUFxSM7nQ5YbI0h+v8viLMYJuvdG2gqCCC+daZbncZ7RFEbXspAj0rlzEwwj5WVK/QCl8g= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1718883089; c=relaxed/simple; bh=W3+iBjaKhb43T0auv7+UBU23kNTmLesKlpzcYTk8EPA=; h=DKIM-Signature:DKIM-Signature:DKIM-Signature:DKIM-Signature:Date: From:To:Subject:Message-ID:MIME-Version; b=rE/nK95bbzzYeKHsrj9FlcgXroQb4s3mLFD6Mo217m6/f2/Acq5uckGvFUJZWlJiRLXRXU7Z4KOk1oayJTGfgfO3Q1mz1k90K3ah+gsb8PGlU5KbnZ39r5DNHPOXomcLb8RwllYISrDGVjceIvz4cVG0uvreZNcXfFdH1194ZbQ= ARC-Authentication-Results: i=1; server2.sourceware.org Received: from imap1.dmz-prg2.suse.org (unknown [10.150.64.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by smtp-out1.suse.de (Postfix) with ESMTPS id C89642195F; Thu, 20 Jun 2024 11:31:23 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.cz; s=susede2_rsa; t=1718883083; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=h4i059K60IKO4Gwl0AuS8t7pWAdirzZ+RHV0ePz12oY=; b=a5LjwUU+PClmTRk961J4J6bAisJontcTeSmx7o8p3jz3oNwO8tO+ngFW48D2XPeq2fTs06 x7IA9wOx4JllYU7kv8qtZtgRR4iA5GQ2wHFlS8yogs7OTR71IYoLVmUuoWjtrmfB20VL6Y 6eJULI+eDgJFIesp6FNvRNHErED3Fbc= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.cz; s=susede2_ed25519; t=1718883083; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=h4i059K60IKO4Gwl0AuS8t7pWAdirzZ+RHV0ePz12oY=; b=hNWRzNYIj3IK0ggQl8P49QPWsjOV+F1OcP3J84KaDI2mdUg/CwAa3SYGBROJYSAm9+x4gV D7U6Y5Wq/ycQA2BQ== Authentication-Results: smtp-out1.suse.de; none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.cz; s=susede2_rsa; t=1718883083; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=h4i059K60IKO4Gwl0AuS8t7pWAdirzZ+RHV0ePz12oY=; b=a5LjwUU+PClmTRk961J4J6bAisJontcTeSmx7o8p3jz3oNwO8tO+ngFW48D2XPeq2fTs06 x7IA9wOx4JllYU7kv8qtZtgRR4iA5GQ2wHFlS8yogs7OTR71IYoLVmUuoWjtrmfB20VL6Y 6eJULI+eDgJFIesp6FNvRNHErED3Fbc= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.cz; s=susede2_ed25519; t=1718883083; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=h4i059K60IKO4Gwl0AuS8t7pWAdirzZ+RHV0ePz12oY=; b=hNWRzNYIj3IK0ggQl8P49QPWsjOV+F1OcP3J84KaDI2mdUg/CwAa3SYGBROJYSAm9+x4gV D7U6Y5Wq/ycQA2BQ== Received: from imap1.dmz-prg2.suse.org (localhost [127.0.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by imap1.dmz-prg2.suse.org (Postfix) with ESMTPS id AE1C813AC1; Thu, 20 Jun 2024 11:31:23 +0000 (UTC) Received: from dovecot-director2.suse.de ([2a07:de40:b281:106:10:150:64:167]) by imap1.dmz-prg2.suse.org with ESMTPSA id eRI5KgsTdGaDIgAAD6G6ig (envelope-from ); Thu, 20 Jun 2024 11:31:23 +0000 Date: Thu, 20 Jun 2024 13:31:22 +0200 From: Michal Jires To: gcc-patches@gcc.gnu.org Cc: hubicka@ucw.cz Subject: [PATCH 4/7 v2] lto: Implement ltrans cache Message-ID: References: <18cc1c3980551ac1881eea6e78811a629c7baa82.1700222403.git.mjires@suse.cz> <788aa123a8fd4bbfa8a80eda37fbacf38ec78c9b.1700222403.git.mjires@suse.cz> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline In-Reply-To: <788aa123a8fd4bbfa8a80eda37fbacf38ec78c9b.1700222403.git.mjires@suse.cz> X-Spam-Score: -3.80 X-Spam-Level: X-Spamd-Result: default: False [-3.80 / 50.00]; BAYES_HAM(-3.00)[100.00%]; NEURAL_HAM_LONG(-1.00)[-1.000]; MID_RHS_NOT_FQDN(0.50)[]; NEURAL_HAM_SHORT(-0.20)[-1.000]; MIME_GOOD(-0.10)[text/plain]; RCPT_COUNT_TWO(0.00)[2]; RCVD_VIA_SMTP_AUTH(0.00)[]; MISSING_XM_UA(0.00)[]; ARC_NA(0.00)[]; RCVD_TLS_ALL(0.00)[]; FUZZY_BLOCKED(0.00)[rspamd.com]; TO_MATCH_ENVRCPT_ALL(0.00)[]; FROM_HAS_DN(0.00)[]; DKIM_SIGNED(0.00)[suse.cz:s=susede2_rsa,suse.cz:s=susede2_ed25519]; FROM_EQ_ENVFROM(0.00)[]; TO_DN_NONE(0.00)[]; RCVD_COUNT_TWO(0.00)[2]; MIME_TRACE(0.00)[0:+] X-Spam-Status: No, score=-12.0 required=5.0 tests=BAYES_00,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,KAM_SHORT,SPF_HELO_NONE,SPF_PASS,TXREP,T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: Outside of suggested changes, this version: - uses #define INCLUDE_* - is rebased onto current trunk - 'fprintf (mstream,' lines - --verbose 'recompiling++' count is moved into correct if branch ___ This patch implements Incremental LTO as ltrans cache. The cache is active when directory $GCC_LTRANS_CACHE is specified and exists. Stored are pairs of ltrans input/output files and input file hash. File locking is used to allow multiple GCC instances to use to same cache. Bootstrapped/regtested on x86_64-pc-linux-gnu gcc/ChangeLog: * Makefile.in: Add lto-ltrans-cache.o. * lto-wrapper.cc: Use ltrans cache. * lto-ltrans-cache.cc: New file. * lto-ltrans-cache.h: New file. --- gcc/Makefile.in | 5 +- gcc/lto-ltrans-cache.cc | 409 ++++++++++++++++++++++++++++++++++++++++ gcc/lto-ltrans-cache.h | 147 +++++++++++++++ gcc/lto-wrapper.cc | 153 +++++++++++++-- 4 files changed, 699 insertions(+), 15 deletions(-) create mode 100644 gcc/lto-ltrans-cache.cc create mode 100644 gcc/lto-ltrans-cache.h diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 90ec59dca75..ab7335ed882 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1855,7 +1855,7 @@ ALL_HOST_BACKEND_OBJS = $(GCC_OBJS) $(OBJS) $(OBJS-libcommon) \ $(OBJS-libcommon-target) main.o c-family/cppspec.o \ $(COLLECT2_OBJS) $(EXTRA_GCC_OBJS) $(GCOV_OBJS) $(GCOV_DUMP_OBJS) \ $(GCOV_TOOL_OBJS) $(GENGTYPE_OBJS) gcc-ar.o gcc-nm.o gcc-ranlib.o \ - lto-wrapper.o collect-utils.o lockfile.o + lto-wrapper.o collect-utils.o lockfile.o lto-ltrans-cache.o # for anything that is shared use the cc1plus profile data, as that # is likely the most exercised during the build @@ -2384,7 +2384,8 @@ collect2$(exeext): $(COLLECT2_OBJS) $(LIBDEPS) CFLAGS-collect2.o += -DTARGET_MACHINE=\"$(target_noncanonical)\" \ @TARGET_SYSTEM_ROOT_DEFINE@ -LTO_WRAPPER_OBJS = lto-wrapper.o collect-utils.o ggc-none.o lockfile.o +LTO_WRAPPER_OBJS = lto-wrapper.o collect-utils.o ggc-none.o lockfile.o \ + lto-ltrans-cache.o lto-wrapper$(exeext): $(LTO_WRAPPER_OBJS) libcommon-target.a $(LIBDEPS) +$(LINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) -o T$@ \ diff --git a/gcc/lto-ltrans-cache.cc b/gcc/lto-ltrans-cache.cc new file mode 100644 index 00000000000..a6ff02afb58 --- /dev/null +++ b/gcc/lto-ltrans-cache.cc @@ -0,0 +1,409 @@ +/* File caching. + Copyright (C) 2023-2024 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#define INCLUDE_ALGORITHM +#define INCLUDE_STRING +#define INCLUDE_ARRAY +#define INCLUDE_MAP +#define INCLUDE_VECTOR +#include "config.h" +#include "system.h" +#include "md5.h" +#include "lto-ltrans-cache.h" + +static const md5_checksum_t INVALID_CHECKSUM = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +/* Computes checksum for given file, returns INVALID_CHECKSUM if not + possible. + */ +static md5_checksum_t +file_checksum (char const *filename) +{ + FILE *file = fopen (filename, "rb"); + + if (!file) + return INVALID_CHECKSUM; + + md5_checksum_t result; + + int ret = md5_stream (file, &result); + + if (ret) + result = INVALID_CHECKSUM; + + fclose (file); + + return result; +} + +/* Checks identity of two files byte by byte. */ +static bool +files_identical (char const *first_filename, char const *second_filename) +{ + FILE *f_first = fopen (first_filename, "rb"); + if (!f_first) + return false; + + FILE *f_second = fopen (second_filename, "rb"); + if (!f_second) + { + fclose (f_first); + return false; + } + + bool ret = true; + + for (;;) + { + int c1, c2; + c1 = fgetc (f_first); + c2 = fgetc (f_second); + + if (c1 != c2) + { + ret = false; + break; + } + + if (c1 == EOF) + break; + } + + fclose (f_first); + fclose (f_second); + return ret; +} + +/* Contructor of cache item. */ +ltrans_file_cache::item::item (std::string input, std::string output, + md5_checksum_t input_checksum, + uint32_t last_used): + input (std::move (input)), output (std::move (output)), + input_checksum (input_checksum), last_used (last_used) +{ + lock = lockfile (this->input + ".lock"); +} +/* Destructor of cache item. */ +ltrans_file_cache::item::~item () +{ + lock.unlock (); +} + +/* Reads next cache item from cachedata file. + Adds `dir/` prefix to filenames. */ +static ltrans_file_cache::item* +read_cache_item (FILE* f, const char* dir) +{ + md5_checksum_t checksum; + uint32_t last_used; + + if (fread (&checksum, 1, checksum.size (), f) != checksum.size ()) + return NULL; + if (fread (&last_used, sizeof (last_used), 1, f) != 1) + return NULL; + + std::string input (dir); + input.push_back ('/'); + std::string output = input; /* Copy. */ + + int c; + while ((c = getc (f))) + { + if (c == EOF) + return NULL; + input.push_back (c); + } + input.push_back (0); + while ((c = getc (f))) + { + if (c == EOF) + return NULL; + output.push_back (c); + } + output.push_back (0); + + return new ltrans_file_cache::item (&input[0], &output[0], checksum, + last_used); +} + +/* Writes cache item to cachedata file. + Removes `dir/` prefix from filenames. */ +static void +write_cache_item (FILE* f, ltrans_file_cache::item *item, const char* dir) +{ + fwrite (&item->input_checksum, 1, item->input_checksum.size (), f); + fwrite (&item->last_used, sizeof (item->last_used), 1, f); + + gcc_assert (item->input.size () > strlen (dir)); + fputs (item->input.c_str () + strlen (dir) + 1, f); + fputc (0, f); + + gcc_assert (item->output.size () > strlen (dir)); + fputs (item->output.c_str () + strlen (dir) + 1, f); + fputc (0, f); +} + +/* Constructor. Resulting cache item filenames will be + in format `prefix%d[.ltrans]suffix`. */ +ltrans_file_cache::ltrans_file_cache (const char* dir, const char* prefix, + const char* suffix) +{ + this->dir = dir; + if (!dir) return; + + creation_lock = lockfile (std::string (dir) + "/lockfile_creation"); + deletion_lock = lockfile (std::string (dir) + "/lockfile_deletion"); + + soft_cache_size = 2048; + + cache_prefix = std::string (dir) + "/" + prefix; + cache_free_idx = 0; + + this->prefix = prefix; + this->suffix = suffix; + + str_buffer = (char *)xmalloc (cache_prefix.size () + + sizeof ("4294967295.ltrans") + + strlen (suffix)); +} + +/* Destructor. */ +ltrans_file_cache::~ltrans_file_cache () +{ + if (!*this) + return; + + cleanup (); + free (str_buffer); +} + +/* Adds given cache item to all relevant datastructures. */ +void +ltrans_file_cache::add_item (ltrans_file_cache::item* item) +{ + items.push_back (item); + map_checksum[item->input_checksum] = item; + map_input[item->input] = item; + + usage_counter = std::max (usage_counter, item->last_used); +} + +/* Creates cachedata filename for save/load. */ +std::string +ltrans_file_cache::filename_cachedata () +{ + return std::string (dir) + "/cachedata"; +} + +/* Loads data about previously cached items from cachedata file. + Sorts items by last_used and remaps last_used to small integers. + + Must be called with creation_lock or deletion_lock held to + prevent data race. */ +void +ltrans_file_cache::load_cache () +{ + cleanup (); + + std::string filename = filename_cachedata (); + FILE *file = fopen (filename.c_str (), "rb"); + + if (!file) + return; + + ltrans_file_cache::item* _item; + while ((_item = read_cache_item (file, dir))) + add_item (_item); + + fclose (file); + + std::sort (items.begin (), items.end (), + [](item* a, item* b) + {return a->last_used < b->last_used;}); + + for (size_t i = 0; i < items.size (); ++i) + items[i]->last_used = (uint32_t) i; + usage_counter = (uint32_t) items.size (); +} + +/* Rewrites data about cache items into cachedata file. + + Must be only called when creation_lock or deletion_lock was held since last + call to load_cache. */ +void +ltrans_file_cache::save_cache () +{ + std::string filename = filename_cachedata (); + FILE *file = fopen (filename.c_str (), "wb"); + + if (!file) + return; + + for (item* _item: items) + write_cache_item (file, _item, dir); + + fclose (file); +} + +/* Creates new cache item with given checksum. + New input/output files are chosen to not collide with other items. + + Must be called with creation_lock held to prevent data race. */ +ltrans_file_cache::item* +ltrans_file_cache::create_item (md5_checksum_t checksum) +{ + size_t prefix_len = cache_prefix.size (); + + strcpy (str_buffer, cache_prefix.c_str ()); + + for (;;) + { + sprintf (str_buffer + prefix_len, "%04u%s", cache_free_idx, suffix); + + if (map_input.find (str_buffer) == map_input.end ()) + break; + cache_free_idx++; + } + + std::string input = str_buffer; + + sprintf (str_buffer + prefix_len, "%04u.ltrans%s", cache_free_idx, suffix); + + return new item (std::move (input), str_buffer, checksum, usage_counter++); +} + +/* Adds input file into cache. Cache item with input file identical to + added input file will be returned as _item. + If the file was already cached, `true` is returned, `false` otherwise. + The added input file is deleted (or moved). + + Must be called with creation_lock held to prevent data race. */ +bool +ltrans_file_cache::add_to_cache (const char* filename, item*& _item) +{ + md5_checksum_t checksum = file_checksum (filename); + + auto it = map_checksum.find (checksum); + + if (it != map_checksum.end () + && files_identical (filename, it->second->input.c_str ())) + { + unlink (filename); + _item = it->second; + _item->last_used = usage_counter++; + return true; + } + else + { + /* Cache miss. Move into cache dir. */ + _item = create_item (checksum); + add_item (_item); + + rename (filename, _item->input.c_str ()); + return false; + } +} + +/* If exists, returns cache item corresponding to cached input file. */ +ltrans_file_cache::item* +ltrans_file_cache::get_item (const char* input) +{ + auto it = map_input.find (input); + if (it == map_input.end ()) + return NULL; + return it->second; +} + +/* If no other process holds the deletion_lock, prunes oldest unused cache + items over limit. */ +void +ltrans_file_cache::try_prune () +{ + if (deletion_lock.try_lock_write () == 0) + { + prune (); + deletion_lock.unlock (); + } +} + +/* Returns true if file exists. */ +static int +file_exists (char const *name) +{ + return access (name, R_OK) == 0; +} + +/* Prunes oldest unused cache items over limit. + Must be called with deletion_lock held to prevent data race. */ +void +ltrans_file_cache::prune () +{ + load_cache (); + if (items.size () > soft_cache_size) + { + std::vector sorted_items = std::move (items); + + cleanup (); + + for (size_t i = 0; i < sorted_items.size (); ++i) + { + ltrans_file_cache::item* item = sorted_items[i]; + if ((i < sorted_items.size () - soft_cache_size) + || !file_exists (item->input.c_str ()) + || !file_exists (item->output.c_str ())) + { + unlink (item->input.c_str ()); + unlink (item->output.c_str ()); + delete item; + } + else + add_item (item); + } + } + save_cache (); +} + +/* Clears cache class, as if only constructor was called. */ +void +ltrans_file_cache::cleanup () +{ + map_checksum.clear (); + map_input.clear (); + + for (ltrans_file_cache::item* item: items) + delete item; + items.clear (); + + usage_counter = 0; +} + + +/* Returns ltrans cache dir. + Returns NULL if ltrans cache is disabled. */ +const char* +get_ltrans_cache_dir () +{ + const char *ltrans_cache_dir = getenv ("GCC_LTRANS_CACHE"); + if (!ltrans_cache_dir || ltrans_cache_dir[0] == '\0' + || !file_exists (ltrans_cache_dir)) + ltrans_cache_dir = NULL; + return ltrans_cache_dir; +} diff --git a/gcc/lto-ltrans-cache.h b/gcc/lto-ltrans-cache.h new file mode 100644 index 00000000000..3cfdfc66630 --- /dev/null +++ b/gcc/lto-ltrans-cache.h @@ -0,0 +1,147 @@ +/* File caching. + Copyright (C) 2023-2024 Free Software Foundation, Inc. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#ifndef GCC_LTO_LTRANS_CACHE_H +#define GCC_LTO_LTRANS_CACHE_H + +#include "lockfile.h" + +using md5_checksum_t = std::array; + +class ltrans_file_cache +{ +public: + /* Cache item representing input/output filename pair. */ + struct item + { + item (std::string input, std::string output, + md5_checksum_t input_checksum, uint32_t last_used); + ~item (); + + /* Full path to input filename. */ + const std::string input; + /* Full path to output filename. */ + const std::string output; + /* Checksum of input file. */ + const md5_checksum_t input_checksum; + + /* Larger last_used corresponds to later usage. */ + uint32_t last_used; + + /* Lockfile so that output file can be created later than input file. */ + lockfile lock; + }; + + /* Constructor. Resulting cache item filenames will be + in format `prefix%d[.ltrans]suffix`. */ + ltrans_file_cache (const char* dir, const char* prefix, const char* suffix); + /* Destructor. */ + ~ltrans_file_cache (); + + /* Loads data about previously cached items from cachedata file. + + Must be called with creation_lock or deletion_lock held to + prevent data race. */ + void load_cache (); + + /* Rewrites data about cache items into cachedata file. + + Must be only called when creation_lock or deletion_lock was held since last + call to load_cache. */ + void save_cache (); + + + /* Adds input file into cache. Cache item with input file identical to + added input file will be returned as _item. + If the file was already cached, `true` is returned, `false` otherwise. + The added input file is deleted (or moved). + + Must be called with creation_lock held to prevent data race. */ + bool add_to_cache (const char* filename, item*& _item); + + /* If exists, returns cache item corresponding to cached input file. */ + item* get_item (const char* input); + + /* If no other process holds the deletion_lock, prunes oldest unused cache + items over limit. */ + void try_prune (); + + /* Clears cache class, as if only constructor was called. */ + void cleanup (); + + /* Cache is enabled if true. */ + operator bool () + { + return dir; + } + + /* Access to already created items can be concurrent with item creation. */ + lockfile creation_lock; + /* Access to already created items cannot be concurrent + with item deletion. */ + lockfile deletion_lock; + + /* Directory of cache. NULL if cache is disabled. */ + const char* dir; +private: + /* Adds given cache item to all relevant datastructures. */ + void add_item (item* item); + + /* Creates new cache item with given checksum. + New input/output files are chosen to not collide with other items. + + Must be called with creation_lock held to prevent data race. */ + item* create_item (md5_checksum_t checksum); + + /* Prunes oldest unused cache items over limit. + Must be called with deletion_lock held to prevent data race. */ + void prune (); + + /* Creates cachedata filename for save/load. */ + std::string filename_cachedata (); + + /* All cache items in current cache. */ + std::vector items; + std::map map_checksum; + std::map map_input; + + /* Cached filenames are in format "prefix%d[.ltrans]suffix". */ + const char* prefix; + const char* suffix; + + /* If cache items count is larger, prune deletes old items. */ + size_t soft_cache_size; + + /* Counter used to populate last_used of items. */ + uint32_t usage_counter; + + /* String in format "dir/prefix". */ + std::string cache_prefix; + /* Lower indices are occupied. */ + uint32_t cache_free_idx; + + /* Buffer for sprintf. */ + char* str_buffer; +}; + +/* Returns ltrans cache dir. + Returns NULL if ltrans cache is disabled. */ +const char* get_ltrans_cache_dir (); + +#endif diff --git a/gcc/lto-wrapper.cc b/gcc/lto-wrapper.cc index 84835a888ef..cf64ad85daf 100644 --- a/gcc/lto-wrapper.cc +++ b/gcc/lto-wrapper.cc @@ -38,6 +38,9 @@ along with GCC; see the file COPYING3. If not see */ #define INCLUDE_STRING +#define INCLUDE_ARRAY +#define INCLUDE_MAP +#define INCLUDE_VECTOR #include "config.h" #include "system.h" #include "coretypes.h" @@ -52,6 +55,7 @@ along with GCC; see the file COPYING3. If not see #include "opts-diagnostic.h" #include "opt-suggestions.h" #include "opts-jobserver.h" +#include "lto-ltrans-cache.h" /* Environment variable, used for passing the names of offload targets from GCC driver to lto-wrapper. */ @@ -79,7 +83,7 @@ static char *flto_out; static unsigned int nr; static int *ltrans_priorities; static char **input_names; -static char **output_names; +static char const**output_names; static char **offload_names; static char *offload_objects_file_name; static char *makefile; @@ -1809,6 +1813,8 @@ cont1: } } + const char *ltrans_cache_dir = get_ltrans_cache_dir (); + /* If object files contain offload sections, but do not contain LTO sections, then there is no need to perform a link-time recompilation, i.e. lto-wrapper is used only for a compilation of offload images. */ @@ -1834,7 +1840,17 @@ cont1: char *dumpbase = concat (dumppfx, "wpa", NULL); obstack_ptr_grow (&argv_obstack, dumpbase); - if (save_temps) + if (ltrans_cache_dir) + { + /* Results of wpa phase must be on the same disk partition as + cache. */ + char* file = concat (ltrans_cache_dir, "/ccXXXXXX.ltrans.out", NULL); + int fd = mkstemps (file, strlen (".ltrans.out")); + gcc_assert (fd != -1 && !close (fd)); + + ltrans_output_file = file; + } + else if (save_temps) ltrans_output_file = concat (dumppfx, "ltrans.out", NULL); else ltrans_output_file = make_temp_file (".ltrans.out"); @@ -1960,7 +1976,8 @@ cont: ltrans_priorities = (int *)xrealloc (ltrans_priorities, nr * sizeof (int) * 2); input_names = (char **)xrealloc (input_names, nr * sizeof (char *)); - output_names = (char **)xrealloc (output_names, nr * sizeof (char *)); + output_names = (char const**) + xrealloc (output_names, nr * sizeof (char const*)); ltrans_priorities[(nr-1)*2] = priority; ltrans_priorities[(nr-1)*2+1] = nr-1; input_names[nr-1] = input_name; @@ -1992,21 +2009,79 @@ cont: qsort (ltrans_priorities, nr, sizeof (int) * 2, cmp_priority); } + ltrans_file_cache ltrans_cache (ltrans_cache_dir, "ltrans", ".o"); + + if (ltrans_cache) + { + if (!lockfile::lockfile_supported ()) + { + warning (0, "using ltrans cache without file locking support," + " do not use in parallel"); + } + ltrans_cache.deletion_lock.lock_read (); + ltrans_cache.creation_lock.lock_write (); + + ltrans_cache.load_cache (); + + int recompiling = 0; + + for (i = 0; i < nr; ++i) + { + /* If it's a pass-through file do nothing. */ + if (output_names[i]) + continue; + + ltrans_file_cache::item* item; + bool existed = ltrans_cache.add_to_cache (input_names[i], item); + free (input_names[i]); + input_names[i] = xstrdup (item->input.c_str ()); + + if (existed) + { + /* Fill the output_name to skip compilation. */ + output_names[i] = item->output.c_str (); + } + else + { + /* Lock so no other process can access until the file is + compiled. */ + item->lock.lock_write (); + recompiling++; + } + } + if (verbose) + fprintf (stderr, "LTRANS: recompiling %d/%d\n", recompiling, nr); + + ltrans_cache.save_cache (); + ltrans_cache.creation_lock.unlock (); + } + /* Execute the LTRANS stage for each input file (or prepare a makefile to invoke this in parallel). */ for (i = 0; i < nr; ++i) { - char *output_name; + char const* output_name; char *input_name = input_names[i]; - /* If it's a pass-through file do nothing. */ + /* If it's a pass-through or cached file do nothing. */ if (output_names[i]) continue; - /* Replace the .o suffix with a .ltrans.o suffix and write - the resulting name to the LTRANS output list. */ - obstack_grow (&env_obstack, input_name, strlen (input_name) - 2); - obstack_grow (&env_obstack, ".ltrans.o", sizeof (".ltrans.o")); - output_name = XOBFINISH (&env_obstack, char *); + if (ltrans_cache) + { + ltrans_file_cache::item* item; + item = ltrans_cache.get_item (input_name); + gcc_assert (item); + + output_name = item->output.c_str (); + } + else + { + /* Replace the .o suffix with a .ltrans.o suffix and write + the resulting name to the LTRANS output list. */ + obstack_grow (&env_obstack, input_name, strlen (input_name) - 2); + obstack_grow (&env_obstack, ".ltrans.o", sizeof (".ltrans.o")); + output_name = XOBFINISH (&env_obstack, char const*); + } /* Adjust the dumpbase if the linker output file was seen. */ int dumpbase_len = (strlen (dumppfx) @@ -2029,7 +2104,7 @@ cont: /* If we are not preserving the ltrans input files then truncate them as soon as we have processed it. This reduces temporary disk-space usage. */ - if (! save_temps) + if (!ltrans_cache && !save_temps) fprintf (mstream, " -truncate '%s'", input_name); fprintf (mstream, "\n"); } @@ -2043,7 +2118,8 @@ cont: "ltrans%u.ltrans_args", i); fork_execute (new_argv[0], CONST_CAST (char **, new_argv), true, save_temps ? argsuffix : NULL); - maybe_unlink (input_name); + if (!ltrans_cache) + maybe_unlink (input_names[i]); } output_names[i] = output_name; @@ -2098,15 +2174,66 @@ cont: freeargv (make_argv); maybe_unlink (makefile); makefile = NULL; + + if (!ltrans_cache) + for (i = 0; i < nr; ++i) + maybe_unlink (input_names[i]); + } + + if (ltrans_cache) + { for (i = 0; i < nr; ++i) - maybe_unlink (input_names[i]); + { + char *input_name = input_names[i]; + char const *output_name = output_names[i]; + + ltrans_file_cache::item* item; + item = ltrans_cache.get_item (input_name); + + if (item && !save_temps) + { + item->lock.lock_read (); + /* Ensure that cached compiled file is not deleted. + Create copy. */ + + obstack_grow (&env_obstack, output_name, + strlen (output_name) - 2); + obstack_grow (&env_obstack, ".cache_copy.XXX.o", + sizeof (".cache_copy.XXX.o")); + + char* output_name_link = XOBFINISH (&env_obstack, char *); + char* name_idx = output_name_link + strlen (output_name_link) + - strlen ("XXX.o"); + + /* lto-wrapper can run in parallel and access + the same partition. */ + for (int j = 0; ; j++) + { + gcc_assert (j < 1000); + sprintf (name_idx, "%03d.o", j); + + if (link (output_name, output_name_link) != EEXIST) + break; + } + + output_names[i] = output_name_link; + item->lock.unlock (); + } + } + + ltrans_cache.deletion_lock.unlock (); } + for (i = 0; i < nr; ++i) { fputs (output_names[i], stdout); putc ('\n', stdout); free (input_names[i]); } + + if (ltrans_cache && !save_temps) + ltrans_cache.try_prune (); + if (!skip_debug) { for (i = 0; i < ltoobj_argc; ++i) -- 2.45.1