From patchwork Fri Nov 17 20:17:11 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michal Jires X-Patchwork-Id: 166317 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:9910:0:b0:403:3b70:6f57 with SMTP id i16csp789249vqn; Fri, 17 Nov 2023 12:17:42 -0800 (PST) X-Google-Smtp-Source: AGHT+IGt8To0qsXYoB/yOpHGLKwjlIMF3FQfSQOxTmhUgBwi+WG9Xg6297wK+t5GsIWSKbCa7yTN X-Received: by 2002:a05:6830:1da4:b0:6b8:807b:b50 with SMTP id z4-20020a0568301da400b006b8807b0b50mr324489oti.22.1700252262518; Fri, 17 Nov 2023 12:17:42 -0800 (PST) ARC-Seal: i=2; a=rsa-sha256; t=1700252262; cv=pass; d=google.com; s=arc-20160816; b=HuDPS1hZTsiW/9uBW+6OOEsFboFQjcI5u4VhCFnJpVkrcjfxenGqrQxpNT3EFrATzr bRSbRQD2P1pgCqu2FIAnrzyRZkKN/0yOEz3JTD8ZiufSrndUPM1QuSFyBeLIy4bxAo8p 3atuFZFjU3PJrK/afIE5xBGh11ay1AXb3qej6cTMytD2omz4vCa3LEEcXY2KDMfkx8lh wtfIGz4u1KI1OWCfnLWwVkLsallsqnvs+NIyLXZFvdN7bBSNThQ+j6AHMfcr4QdvxFhd xMF7Fdf142ahUJx9Sh9X2XlXCHGs7GYPDvqrjHqpBp9hwiEJKcsMoZ4dko445gFNxhBS Qvxw== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=errors-to:list-subscribe:list-help:list-post:list-archive :list-unsubscribe:list-id:precedence:in-reply-to:content-disposition :mime-version:references:message-id:subject:to:from:date :dkim-signature:dkim-signature:arc-filter:dmarc-filter:delivered-to; bh=XsBmjZEwUiQ2z2tqsVn7uKN+61p6JBz3Jjuio+NxHDI=; fh=hPrbWPhweUx4V0GV9uXJqbyAzg2ABmTz7kczrAQqMmM=; b=DexlKMuJZCXsg1A7d7z77TxP50FhsmS+yzmZZgFlK9vSDZDxmMKyBhqu07xljlEc4L ywdnWAejzjgg+Olbji9x+0dEAYFpcR/9QpRkMozv3y+EuDObbrFWhwFa3SWmjcxMq9Fn 44glXmTSsIenjJ/dFdjSqvl3B4ZbKMhTYvHOlPsgVHhvWKBl4O3niT9cC5ro6uNZLrnF CWJGWAhBODYxMppH7iuyLLPxuKfONAaZnvxV1KNGr4S7H1ruGkGh+HxI6BRvWbxOMJu+ X4gd6IIQq07cnbHH/xRnC+kKvhfqz10/vV8aHpcCR3v79WUUCAZiN2Y+rn6O99DVAMf+ y5gQ== ARC-Authentication-Results: i=2; mx.google.com; dkim=pass header.i=@suse.cz header.s=susede2_rsa header.b=Q0wL04W0; dkim=neutral (no key) header.i=@suse.cz header.s=susede2_ed25519 header.b="t/piFz1o"; arc=pass (i=1); spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 8.43.85.97 as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org" Received: from server2.sourceware.org (server2.sourceware.org. [8.43.85.97]) by mx.google.com with ESMTPS id n8-20020a0cfbc8000000b00670b3a8241bsi2293603qvp.350.2023.11.17.12.17.42 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 17 Nov 2023 12:17:42 -0800 (PST) Received-SPF: pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 8.43.85.97 as permitted sender) client-ip=8.43.85.97; Authentication-Results: mx.google.com; dkim=pass header.i=@suse.cz header.s=susede2_rsa header.b=Q0wL04W0; dkim=neutral (no key) header.i=@suse.cz header.s=susede2_ed25519 header.b="t/piFz1o"; arc=pass (i=1); spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 8.43.85.97 as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org" Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 930093857C41 for ; Fri, 17 Nov 2023 20:17:41 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.220.29]) by sourceware.org (Postfix) with ESMTPS id 425E438582B1 for ; Fri, 17 Nov 2023 20:17:14 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 425E438582B1 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 425E438582B1 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=195.135.220.29 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1700252237; cv=none; b=t1YXTf9+pz4mnvU4iPQuLO3c+IrLA+SbL8a1hjym3deJAkL6Fq6/ieRFCYrnPEXDUR1inDw0uWFWOw2whYVNLahaoKPBRz0WHnvdnyfRaAlJjRp+/A+ObtNjksihAQ75Bc5yetItI5vORnwzcE8PnTMIawCrlvq3BxbmL8VIdI8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1700252237; c=relaxed/simple; bh=YxB5uc9+Oq3mieuQ/GCSRk+B+Ri5Oq8QlhG4osN76zE=; h=DKIM-Signature:DKIM-Signature:Date:From:To:Subject:Message-ID: MIME-Version; b=Dk7DBvIwIKdK+E9fmrdsYwLkKxIW1qDo5QKJG8g+jfl4hplpB76H8U/KJxjZHXtFiQfjOJUqlwq+OkTWT6J29YwSHCt7oN3skL+ideM+DaV1Kj9lYSTizsatimq3EYgV5wSlchYIZik9l2zF/sp8c5eGCxtvJH0kJhyw+bhjCtM= ARC-Authentication-Results: i=1; server2.sourceware.org Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id 5F7251F37E for ; Fri, 17 Nov 2023 20:17:13 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.cz; s=susede2_rsa; t=1700252233; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=XsBmjZEwUiQ2z2tqsVn7uKN+61p6JBz3Jjuio+NxHDI=; b=Q0wL04W0dfQSclz2MPeTjqTFIbLmWa5iNoeaAWj23shKZeufdBqIDkyAC+4yJ5NBW4Vs23 jolOoBiVq8T77bUgyGbY/dr7g8QTnkGjcGSRaqyVK55k3ZxL6VkGGFEeHLmC+XWewAdkSh AsCPDXN6ujIrkIP12a7sHIkfx5VG+xU= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.cz; s=susede2_ed25519; t=1700252233; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc: mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=XsBmjZEwUiQ2z2tqsVn7uKN+61p6JBz3Jjuio+NxHDI=; b=t/piFz1oRDen5b5iKKIRuZw5n9QZxi95QqayAlIvHMEFNVws6k5tTS4LyuNchj1mHmxOoo DVjJLkFrLMIwXiDg== Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by imap2.suse-dmz.suse.de (Postfix) with ESMTPS id 4231F1341F for ; Fri, 17 Nov 2023 20:17:13 +0000 (UTC) Received: from dovecot-director2.suse.de ([192.168.254.65]) by imap2.suse-dmz.suse.de with ESMTPSA id J6F4DknKV2UvGgAAMHmgww (envelope-from ) for ; Fri, 17 Nov 2023 20:17:13 +0000 Date: Fri, 17 Nov 2023 21:17:11 +0100 From: Michal Jires To: gcc-patches@gcc.gnu.org Subject: [PATCH 4/7] lto: Implement ltrans cache Message-ID: <788aa123a8fd4bbfa8a80eda37fbacf38ec78c9b.1700222403.git.mjires@suse.cz> References: <18cc1c3980551ac1881eea6e78811a629c7baa82.1700222403.git.mjires@suse.cz> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: <18cc1c3980551ac1881eea6e78811a629c7baa82.1700222403.git.mjires@suse.cz> Authentication-Results: smtp-out2.suse.de; none X-Spam-Level: X-Spam-Score: -3.29 X-Spamd-Result: default: False [-3.29 / 50.00]; ARC_NA(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[]; FROM_HAS_DN(0.00)[]; TO_MATCH_ENVRCPT_ALL(0.00)[]; NEURAL_HAM_LONG(-1.00)[-1.000]; MIME_GOOD(-0.10)[text/plain]; PREVIOUSLY_DELIVERED(0.00)[gcc-patches@gcc.gnu.org]; TO_DN_NONE(0.00)[]; RCPT_COUNT_ONE(0.00)[1]; DKIM_SIGNED(0.00)[suse.cz:s=susede2_rsa,suse.cz:s=susede2_ed25519]; NEURAL_HAM_SHORT(-0.19)[-0.937]; MID_CONTAINS_FROM(1.00)[]; FUZZY_BLOCKED(0.00)[rspamd.com]; FROM_EQ_ENVFROM(0.00)[]; MIME_TRACE(0.00)[0:+]; RCVD_COUNT_TWO(0.00)[2]; RCVD_TLS_ALL(0.00)[]; BAYES_HAM(-3.00)[100.00%] X-Spam-Status: No, score=-12.3 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 X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1782843716750708078 X-GMAIL-MSGID: 1782843716750708078 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 | 407 ++++++++++++++++++++++++++++++++++++++++ gcc/lto-ltrans-cache.h | 164 ++++++++++++++++ gcc/lto-wrapper.cc | 150 +++++++++++++-- 4 files changed, 711 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 2c527245c81..495e5f3d069 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1831,7 +1831,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 @@ -2359,7 +2359,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..0d43e548fb3 --- /dev/null +++ b/gcc/lto-ltrans-cache.cc @@ -0,0 +1,407 @@ +/* File caching. + Copyright (C) 2009-2023 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 +. */ + +#include "config.h" +#include "system.h" +#include "md5.h" +#include "lto-ltrans-cache.h" + +#include +#include +#include + +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::vector input (strlen (dir)); + memcpy (&input[0], dir, input.size ()); + input.push_back ('/'); + std::vector 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..763a23635f7 --- /dev/null +++ b/gcc/lto-ltrans-cache.h @@ -0,0 +1,164 @@ +/* File caching. + Copyright (C) 2009-2023 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 +#include +#include +#include +#include + +#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 5186d040ce0..4b98539067e 100644 --- a/gcc/lto-wrapper.cc +++ b/gcc/lto-wrapper.cc @@ -52,6 +52,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 +80,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; @@ -1802,6 +1803,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. */ @@ -1827,7 +1830,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"); @@ -1953,7 +1966,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; @@ -1985,21 +1999,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 (); + recompiling++; + } + else + { + /* Lock so no other process can access until the file is + compiled. */ + item->lock.lock_write (); + } + } + 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) @@ -2023,7 +2095,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, "\t@-touch -r \"%s\" \"%s.tem\" > /dev/null " "2>&1 && mv \"%s.tem\" \"%s\"\n", input_name, input_name, input_name, input_name); @@ -2038,7 +2110,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; @@ -2093,15 +2166,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)