diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 868a581..d267897 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -144,6 +144,7 @@ else() # UNIX /guard:cf /Zi /W3 + /utf-8 ) set(COMMON_LINKER_ARGS @@ -213,6 +214,7 @@ add_link_options("$<$:${XA_UTILS_CXX_LINKER_FLAGS}>") add_link_options("$<$:${XA_UTILS_C_LINKER_FLAGS}>") if(WIN32) + add_compile_definitions(_UNICODE) include_directories(compat) endif() diff --git a/src/compat/getopt.c b/src/compat/getopt.c deleted file mode 100644 index ac7f9fe..0000000 --- a/src/compat/getopt.c +++ /dev/null @@ -1,562 +0,0 @@ -/* $OpenBSD: getopt_long.c,v 1.23 2007/10/31 12:34:57 chl Exp $ */ -/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ - -/* - * Copyright (c) 2002 Todd C. Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * Sponsored in part by the Defense Advanced Research Projects - * Agency (DARPA) and Air Force Research Laboratory, Air Force - * Materiel Command, USAF, under agreement number F39502-99-1-0512. - */ -/*- - * Copyright (c) 2000 The NetBSD Foundation, Inc. - * All rights reserved. - * - * This code is derived from software contributed to The NetBSD Foundation - * by Dieter Baron and Thomas Klausner. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */ - -#ifdef REPLACE_GETOPT -int opterr = 1; /* if error message should be printed */ -int optind = 1; /* index into parent argv vector */ -int optopt = '?'; /* character checked for validity */ -#undef optreset /* see getopt.h */ -#define optreset __mingw_optreset -int optreset; /* reset getopt */ -char *optarg; /* argument associated with option */ -#endif - -#define PRINT_ERROR ((opterr) && (*options != ':')) - -#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ -#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ -#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ - -/* return values */ -#define BADCH (int)'?' -#define BADARG ((*options == ':') ? (int)':' : (int)'?') -#define INORDER (int)1 - -#ifndef __CYGWIN__ -#define __progname __argv[0] -#else -extern char __declspec(dllimport) *__progname; -#endif - -#ifdef __CYGWIN__ -static char EMSG[] = ""; -#else -#define EMSG "" -#endif - -static int getopt_internal(int, char * const *, const char *, - const struct option *, int *, int); -static int parse_long_options(char * const *, const char *, - const struct option *, int *, int); -static int gcd(int, int); -static void permute_args(int, int, int, char * const *); - -static char *place = EMSG; /* option letter processing */ - -/* XXX: set optreset to 1 rather than these two */ -static int nonopt_start = -1; /* first non option argument (for permute) */ -static int nonopt_end = -1; /* first option after non options (for permute) */ - -/* Error messages */ -static const char recargchar[] = "option requires an argument -- %c"; -static const char recargstring[] = "option requires an argument -- %s"; -static const char ambig[] = "ambiguous option -- %.*s"; -static const char noarg[] = "option doesn't take an argument -- %.*s"; -static const char illoptchar[] = "unknown option -- %c"; -static const char illoptstring[] = "unknown option -- %s"; - -static void -_vwarnx(const char *fmt,va_list ap) -{ - (void)fprintf(stderr,"%s: ",__progname); - if (fmt != NULL) - (void)vfprintf(stderr,fmt,ap); - (void)fprintf(stderr,"\n"); -} - -static void -warnx(const char *fmt,...) -{ - va_list ap; - va_start(ap,fmt); - _vwarnx(fmt,ap); - va_end(ap); -} - -/* - * Compute the greatest common divisor of a and b. - */ -static int -gcd(int a, int b) -{ - int c; - - c = a % b; - while (c != 0) { - a = b; - b = c; - c = a % b; - } - - return (b); -} - -/* - * Exchange the block from nonopt_start to nonopt_end with the block - * from nonopt_end to opt_end (keeping the same order of arguments - * in each block). - */ -static void -permute_args(int panonopt_start, int panonopt_end, int opt_end, - char * const *nargv) -{ - int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; - char *swap; - - /* - * compute lengths of blocks and number and size of cycles - */ - nnonopts = panonopt_end - panonopt_start; - nopts = opt_end - panonopt_end; - ncycle = gcd(nnonopts, nopts); - cyclelen = (opt_end - panonopt_start) / ncycle; - - for (i = 0; i < ncycle; i++) { - cstart = panonopt_end+i; - pos = cstart; - for (j = 0; j < cyclelen; j++) { - if (pos >= panonopt_end) - pos -= nnonopts; - else - pos += nopts; - swap = nargv[pos]; - /* LINTED const cast */ - ((char **) nargv)[pos] = nargv[cstart]; - /* LINTED const cast */ - ((char **)nargv)[cstart] = swap; - } - } -} - -/* - * parse_long_options -- - * Parse long options in argc/argv argument vector. - * Returns -1 if short_too is set and the option does not match long_options. - */ -static int -parse_long_options(char * const *nargv, const char *options, - const struct option *long_options, int *idx, int short_too) -{ - char *current_argv, *has_equal; - size_t current_argv_len; - int i, ambiguous, match; - -#define IDENTICAL_INTERPRETATION(_x, _y) \ - (long_options[(_x)].has_arg == long_options[(_y)].has_arg && \ - long_options[(_x)].flag == long_options[(_y)].flag && \ - long_options[(_x)].val == long_options[(_y)].val) - - current_argv = place; - match = -1; - ambiguous = 0; - - optind++; - - if ((has_equal = strchr(current_argv, '=')) != NULL) { - /* argument found (--option=arg) */ - current_argv_len = has_equal - current_argv; - has_equal++; - } else - current_argv_len = strlen(current_argv); - - for (i = 0; long_options[i].name; i++) { - /* find matching long option */ - if (strncmp(current_argv, long_options[i].name, - current_argv_len)) - continue; - - if (strlen(long_options[i].name) == current_argv_len) { - /* exact match */ - match = i; - ambiguous = 0; - break; - } - /* - * If this is a known short option, don't allow - * a partial match of a single character. - */ - if (short_too && current_argv_len == 1) - continue; - - if (match == -1) /* partial match */ - match = i; - else if (!IDENTICAL_INTERPRETATION(i, match)) - ambiguous = 1; - } - if (ambiguous) { - /* ambiguous abbreviation */ - if (PRINT_ERROR) - warnx(ambig, (int)current_argv_len, - current_argv); - optopt = 0; - return (BADCH); - } - if (match != -1) { /* option found */ - if (long_options[match].has_arg == no_argument - && has_equal) { - if (PRINT_ERROR) - warnx(noarg, (int)current_argv_len, - current_argv); - /* - * XXX: GNU sets optopt to val regardless of flag - */ - if (long_options[match].flag == NULL) - optopt = long_options[match].val; - else - optopt = 0; - return (BADARG); - } - if (long_options[match].has_arg == required_argument || - long_options[match].has_arg == optional_argument) { - if (has_equal) - optarg = has_equal; - else if (long_options[match].has_arg == - required_argument) { - /* - * optional argument doesn't use next nargv - */ - optarg = nargv[optind++]; - } - } - if ((long_options[match].has_arg == required_argument) - && (optarg == NULL)) { - /* - * Missing argument; leading ':' indicates no error - * should be generated. - */ - if (PRINT_ERROR) - warnx(recargstring, - current_argv); - /* - * XXX: GNU sets optopt to val regardless of flag - */ - if (long_options[match].flag == NULL) - optopt = long_options[match].val; - else - optopt = 0; - --optind; - return (BADARG); - } - } else { /* unknown option */ - if (short_too) { - --optind; - return (-1); - } - if (PRINT_ERROR) - warnx(illoptstring, current_argv); - optopt = 0; - return (BADCH); - } - if (idx) - *idx = match; - if (long_options[match].flag) { - *long_options[match].flag = long_options[match].val; - return (0); - } else - return (long_options[match].val); -#undef IDENTICAL_INTERPRETATION -} - -/* - * getopt_internal -- - * Parse argc/argv argument vector. Called by user level routines. - */ -static int -getopt_internal(int nargc, char * const *nargv, const char *options, - const struct option *long_options, int *idx, int flags) -{ - char *oli; /* option letter list index */ - int optchar, short_too; - static int posixly_correct = -1; - - if (options == NULL) - return (-1); - - /* - * XXX Some GNU programs (like cvs) set optind to 0 instead of - * XXX using optreset. Work around this braindamage. - */ - if (optind == 0) - optind = optreset = 1; - - /* - * Disable GNU extensions if POSIXLY_CORRECT is set or options - * string begins with a '+'. - * - * CV, 2009-12-14: Check POSIXLY_CORRECT anew if optind == 0 or - * optreset != 0 for GNU compatibility. - */ - if (posixly_correct == -1 || optreset != 0) - posixly_correct = (GetEnvironmentVariableW(L"POSIXLY_CORRECT", NULL, 0) != 0); - if (*options == '-') - flags |= FLAG_ALLARGS; - else if (posixly_correct || *options == '+') - flags &= ~FLAG_PERMUTE; - if (*options == '+' || *options == '-') - options++; - - optarg = NULL; - if (optreset) - nonopt_start = nonopt_end = -1; -start: - if (optreset || !*place) { /* update scanning pointer */ - optreset = 0; - if (optind >= nargc) { /* end of argument vector */ - place = EMSG; - if (nonopt_end != -1) { - /* do permutation, if we have to */ - permute_args(nonopt_start, nonopt_end, - optind, nargv); - optind -= nonopt_end - nonopt_start; - } - else if (nonopt_start != -1) { - /* - * If we skipped non-options, set optind - * to the first of them. - */ - optind = nonopt_start; - } - nonopt_start = nonopt_end = -1; - return (-1); - } - if (*(place = nargv[optind]) != '-' || - (place[1] == '\0' && strchr(options, '-') == NULL)) { - place = EMSG; /* found non-option */ - if (flags & FLAG_ALLARGS) { - /* - * GNU extension: - * return non-option as argument to option 1 - */ - optarg = nargv[optind++]; - return (INORDER); - } - if (!(flags & FLAG_PERMUTE)) { - /* - * If no permutation wanted, stop parsing - * at first non-option. - */ - return (-1); - } - /* do permutation */ - if (nonopt_start == -1) - nonopt_start = optind; - else if (nonopt_end != -1) { - permute_args(nonopt_start, nonopt_end, - optind, nargv); - nonopt_start = optind - - (nonopt_end - nonopt_start); - nonopt_end = -1; - } - optind++; - /* process next argument */ - goto start; - } - if (nonopt_start != -1 && nonopt_end == -1) - nonopt_end = optind; - - /* - * If we have "-" do nothing, if "--" we are done. - */ - if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { - optind++; - place = EMSG; - /* - * We found an option (--), so if we skipped - * non-options, we have to permute. - */ - if (nonopt_end != -1) { - permute_args(nonopt_start, nonopt_end, - optind, nargv); - optind -= nonopt_end - nonopt_start; - } - nonopt_start = nonopt_end = -1; - return (-1); - } - } - - /* - * Check long options if: - * 1) we were passed some - * 2) the arg is not just "-" - * 3) either the arg starts with -- we are getopt_long_only() - */ - if (long_options != NULL && place != nargv[optind] && - (*place == '-' || (flags & FLAG_LONGONLY))) { - short_too = 0; - if (*place == '-') - place++; /* --foo long option */ - else if (*place != ':' && strchr(options, *place) != NULL) - short_too = 1; /* could be short option too */ - - optchar = parse_long_options(nargv, options, long_options, - idx, short_too); - if (optchar != -1) { - place = EMSG; - return (optchar); - } - } - - if ((optchar = (int)*place++) == (int)':' || - (optchar == (int)'-' && *place != '\0') || - (oli = strchr(options, optchar)) == NULL) { - /* - * If the user specified "-" and '-' isn't listed in - * options, return -1 (non-option) as per POSIX. - * Otherwise, it is an unknown option character (or ':'). - */ - if (optchar == (int)'-' && *place == '\0') - return (-1); - if (!*place) - ++optind; - if (PRINT_ERROR) - warnx(illoptchar, optchar); - optopt = optchar; - return (BADCH); - } - if (long_options != NULL && optchar == 'W' && oli[1] == ';') { - /* -W long-option */ - if (*place) /* no space */ - /* NOTHING */; - else if (++optind >= nargc) { /* no arg */ - place = EMSG; - if (PRINT_ERROR) - warnx(recargchar, optchar); - optopt = optchar; - return (BADARG); - } else /* white space */ - place = nargv[optind]; - optchar = parse_long_options(nargv, options, long_options, - idx, 0); - place = EMSG; - return (optchar); - } - if (*++oli != ':') { /* doesn't take argument */ - if (!*place) - ++optind; - } else { /* takes (optional) argument */ - optarg = NULL; - if (*place) /* no white space */ - optarg = place; - else if (oli[1] != ':') { /* arg not optional */ - if (++optind >= nargc) { /* no arg */ - place = EMSG; - if (PRINT_ERROR) - warnx(recargchar, optchar); - optopt = optchar; - return (BADARG); - } else - optarg = nargv[optind]; - } - place = EMSG; - ++optind; - } - /* dump back option letter */ - return (optchar); -} - -#ifdef REPLACE_GETOPT -/* - * getopt -- - * Parse argc/argv argument vector. - * - * [eventually this will replace the BSD getopt] - */ -int -getopt(int nargc, char * const *nargv, const char *options) -{ - - /* - * We don't pass FLAG_PERMUTE to getopt_internal() since - * the BSD getopt(3) (unlike GNU) has never done this. - * - * Furthermore, since many privileged programs call getopt() - * before dropping privileges it makes sense to keep things - * as simple (and bug-free) as possible. - */ - return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); -} -#endif /* REPLACE_GETOPT */ - -/* - * getopt_long -- - * Parse argc/argv argument vector. - */ -int -getopt_long(int nargc, char * const *nargv, const char *options, - const struct option *long_options, int *idx) -{ - - return (getopt_internal(nargc, nargv, options, long_options, idx, - FLAG_PERMUTE)); -} - -/* - * getopt_long_only -- - * Parse argc/argv argument vector. - */ -int -getopt_long_only(int nargc, char * const *nargv, const char *options, - const struct option *long_options, int *idx) -{ - - return (getopt_internal(nargc, nargv, options, long_options, idx, - FLAG_PERMUTE|FLAG_LONGONLY)); -} diff --git a/src/gas/CMakeLists.txt b/src/gas/CMakeLists.txt index ced38a4..7cc7d3e 100644 --- a/src/gas/CMakeLists.txt +++ b/src/gas/CMakeLists.txt @@ -1,4 +1,5 @@ set(GAS_DRIVER_SOURCES + command_line.cc gas.cc llvm_mc_runner.cc llvm_mc_runner_arm32.cc @@ -18,7 +19,6 @@ set(ARCH_PREFIXES if(WIN32) list(APPEND GAS_DRIVER_SOURCES - ../compat/getopt.c gas.windows.cc process.windows.cc ) diff --git a/src/gas/command_line.cc b/src/gas/command_line.cc new file mode 100644 index 0000000..bf59ebe --- /dev/null +++ b/src/gas/command_line.cc @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include "command_line.hh" +#include "exceptions.hh" +#include "platform.hh" + +using namespace xamarin::android::gas; +namespace ranges = std::ranges; + +bool CommandLine::parse (std::span options, std::vector &args, OptionCallbackFn option_cb) +{ + uint32_t positional_count = 0; + bool next_arg_is_value = false; + std::optional last_opt; + + for (size_t i = 1; i < args.size (); i++) { + platform::string &option = args[i]; + if (option.empty ()) { + continue; + } + + platform::string::const_iterator iter = option.cbegin (); + while (iter != option.cend () && *iter == DASH) { + iter++; + } + + if (next_arg_is_value) { + // getopt takes the next argument verbatim, if separated from the option requiring a value by a space. + // This is done regardless of whether or not the next argument is prefixed with `-` or is a known one. + if (last_opt.has_value ()) { + option_cb (last_opt.value (), option); + } else { + throw invalid_argument_error { "Option '" + option + "' requires an argument." }; + } + + next_arg_is_value = false; + last_opt.reset (); + continue; + } + + if (iter == option.cbegin ()) { // positional + option_cb ({ positional_count++ }, { option }); + continue; + } + + platform::string::const_iterator name_start = iter; + while (iter != option.cend () && *iter != EQUALS) { + iter++; + } + +#if !defined (__APPLE__) + platform::string_view option_name { name_start, iter }; +#else + platform::string_view option_name (option.data () + (name_start - option.cbegin ()), iter - name_start); +#endif + platform::string_view option_value; + + if (iter != option.cend ()) { // has a value + iter++; +#if !defined (__APPLE__) + option_value = { iter, option.cend () }; +#else + option_value = platform::string_view (option.data () + (iter - option.cbegin ()), option.cend () - iter); +#endif + } + + auto matching_option = [this, &option_name] (CommandLineOption const& o) -> bool { + if (o.name != option_name) { + return false; + } + + return o.arch == TargetArchitecture::Any || o.arch == target_arch; + }; + +#if !defined(__APPLE__) + auto match = ranges::find_if (options, matching_option); +#else + // C++ standard library on mac CI doesn't have std::ranges::find_if + auto match = std::find_if (options.begin (), options.end (), matching_option); +#endif + if (match == options.end ()) { + STDERR << "Uncrecognized option '" << option << "'\n"; + continue; + } + + CommandLineOption opt = *match; + if (opt.argument == ArgumentValue::Required && option_value.empty ()) { + next_arg_is_value = true; + last_opt = opt; + continue; + } + + option_cb (opt, platform::string {option_value}); + } + + if (last_opt.has_value ()) { + platform::string message { "Option '" }; + message + .append (last_opt.value ().name) + .append ("' requires an argument."); + throw invalid_operation_error {message}; + } + + return true; +} diff --git a/src/gas/command_line.hh b/src/gas/command_line.hh new file mode 100644 index 0000000..a4f57e6 --- /dev/null +++ b/src/gas/command_line.hh @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT +#if !defined (GAS_COMMAND_LINE_HH) +#define GAS_COMMAND_LINE_HH + +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#endif + +#include "constants.hh" +#include "platform.hh" + +namespace xamarin::android::gas +{ + enum class ArgumentValue + { + Required, + NotRequired, + }; + + enum class OptionId : uint32_t + { + Ignore = 100, + O, + Warn, + G, + MFPU, + Version, + VersionExit, + Help, + }; + + struct CommandLineOption + { + platform::string_view name; + OptionId id; + ArgumentValue argument; + TargetArchitecture arch; + + constexpr CommandLineOption (platform::string_view const& _name, OptionId _id, ArgumentValue _argument, TargetArchitecture _arch) + : name (_name), + id (_id), + argument (_argument), + arch (_arch) + {} + + constexpr CommandLineOption (platform::string_view const& _name, OptionId _id, TargetArchitecture _arch) + : CommandLineOption (_name, _id, ArgumentValue::NotRequired, _arch) + {} + + constexpr CommandLineOption (platform::string_view const& _name, OptionId _id, ArgumentValue _argument) + : CommandLineOption (_name, _id, _argument, TargetArchitecture::Any) + {} + + constexpr CommandLineOption (platform::string_view const& _name, OptionId _id) + : CommandLineOption (_name, _id, ArgumentValue::NotRequired, TargetArchitecture::Any) + {} + }; + + class CommandLine + { +#if !defined(_WIN32) + static constexpr bool is_windows = false; +#else + static constexpr bool is_windows = true; +#endif + + public: +#if !defined(_WIN32) + using TArgType = char*; + + private: + static inline constexpr char DASH = PCHAR('-'); + static inline constexpr char EQUALS = PCHAR('='); +#else + using TArgType = LPWSTR; + + private: + static inline constexpr wchar_t DASH = PCHAR('-'); + static inline constexpr wchar_t EQUALS = PCHAR('='); +#endif + + public: + using TOptionValue = std::variant; + using TCallbackOption = std::variant; + using OptionCallbackFn = std::function; + + public: + explicit CommandLine (TargetArchitecture _target_arch) noexcept + : target_arch (_target_arch) + {} + + template + bool parse (std::array const& options, std::vector &args, OptionCallbackFn option_cb) + { + return parse (std::span (options.data(), options.size()), args, option_cb); + } + + private: + bool parse (std::span options, std::vector &args, OptionCallbackFn option_cb); + + private: + TargetArchitecture target_arch; + }; +} + +#define CLISTR(_str_lit_) PSTR((_str_lit_)) + +#if !defined (_WIN32) +#define CLIPARAM(_str_lit_) std::string_view { _str_lit_ } +#else +#define CLIPARAM(_str_lit_) std::string_view { _str_lit_ } +#endif + +#endif // ndef GAS_COMMAND_LINE_HH diff --git a/src/gas/constants.hh b/src/gas/constants.hh index 6c28835..583ba5a 100644 --- a/src/gas/constants.hh +++ b/src/gas/constants.hh @@ -2,20 +2,22 @@ #if !defined (__CONSTANTS_HH) #define __CONSTANTS_HH +#include "platform.hh" + namespace xamarin::android::gas { class Constants final { public: #if defined (_WIN32) - static constexpr char newline[] = "\r\n"; - static constexpr char llvm_mc_name[] = "llvm-mc.exe"; + static constexpr platform::string_view newline { PSTR("\r\n") }; + static constexpr platform::string_view llvm_mc_name { PSTR("llvm-mc.exe") }; #else - static constexpr char newline[] = "\n"; - static constexpr char llvm_mc_name[] = "llvm-mc"; + static constexpr platform::string_view newline { "\n" }; + static constexpr platform::string_view llvm_mc_name { "llvm-mc" }; #endif - static constexpr char arch_hack_param[] = "@gas-arch="; - static constexpr char default_output_name[] = "a.out"; + static constexpr platform::string_view arch_hack_param { PSTR("@gas-arch=") }; + static constexpr platform::string_view default_output_name { PSTR("a.out") }; static constexpr int wrapper_general_error_code = 100; static constexpr int wrapper_llvm_mc_killed_error_code = wrapper_general_error_code + 1; static constexpr int wrapper_llvm_mc_stopped_error_code = wrapper_general_error_code + 2; @@ -23,5 +25,14 @@ namespace xamarin::android::gas static constexpr int wrapper_exec_failed_error_code = wrapper_general_error_code + 4; static constexpr int wrapper_wait_failed_error_code = wrapper_general_error_code + 5; }; + + enum class TargetArchitecture + { + Any, + ARM32, + ARM64, + X86, + X64, + }; } #endif diff --git a/src/gas/gas.cc b/src/gas/gas.cc index 7efd933..2b0759a 100644 --- a/src/gas/gas.cc +++ b/src/gas/gas.cc @@ -3,23 +3,25 @@ #include #endif +#include #include #include #include +#include "command_line.hh" #include "constants.hh" #include "gas.hh" #include "llvm_mc_runner.hh" using namespace xamarin::android::gas; -int Gas::usage (bool is_error, std::string const message) +int Gas::usage (bool is_error, platform::string const message) { if (!message.empty ()) { - std::cerr << message << Constants::newline << Constants::newline; + STDERR << message << Constants::newline << Constants::newline; } - std::cerr << "`" << program_name () << "` takes a subset of arguments accepted by the GNU Assembler (gas) program." << Constants::newline + STDERR << "`" << program_name () << "` takes a subset of arguments accepted by the GNU Assembler (gas) program." << Constants::newline << "Accepted options are limited to the ones used by Xamarin.Android and Mono/dotnet AOT, more can be added as-needed." << Constants::newline << "Some options are accepted but ignored, either because they are ignored by GAS as well or because they are used by" << Constants::newline << "Xamarin.Android or Mono/dotnet AOT, but not needed by `llvm-mc`. All the unsupported options will cause the wrapper" << Constants::newline @@ -56,29 +58,39 @@ int Gas::usage (bool is_error, std::string const message) return is_error ? 1 : 0; } -int Gas::run (int argc, char **argv) +std::vector Gas::get_command_line (int &argc, char **&argv) { - determine_program_dir (argc, argv); + std::vector ret; - auto lowercase_string = [](std::string& s) { + for (int i = 0; i < argc; i++) { + ret.emplace_back (argv[i]); + } + + return ret; +} + +int Gas::run (std::vector args) +{ + determine_program_dir (args); + auto lowercase_string = [](platform::string& s) { std::transform ( s.begin (), s.end (), s.begin (), - [](unsigned char c) { return std::tolower(c); } + [](platform::string::value_type c) { return std::tolower(c); } ); }; - std::string arch_name { generic_gas_name }; - const char *first_param = argc > 1 ? argv[1] : nullptr; - if (first_param != nullptr && strlen (first_param) > sizeof(Constants::arch_hack_param) && strstr (first_param, Constants::arch_hack_param) == first_param) { - arch_name = first_param + (sizeof(Constants::arch_hack_param) - 1); + platform::string arch_name { generic_gas_name }; + platform::string first_param { args.size () > 1 ? args[1] : PSTR("") }; + if (!first_param.empty () && first_param.length () > Constants::arch_hack_param.size () && first_param.find (Constants::arch_hack_param) == 0) { + arch_name = first_param.substr (Constants::arch_hack_param.size ()); lowercase_string (arch_name); } _program_name = arch_name; std::unique_ptr mc_runner; - std::string ld_name; + platform::string ld_name; if (arch_name.compare (arm64_gas_name.data ()) == 0) { _target_arch = TargetArchitecture::ARM64; mc_runner = std::make_unique (); @@ -96,34 +108,36 @@ int Gas::run (int argc, char **argv) mc_runner = std::make_unique (); ld_name = x64_ld_name.data (); } else if (arch_name.compare (generic_gas_name) == 0) { - std::string message { "Program invoked via its generic name (" }; + platform::string message { PSTR("Program invoked via its generic name (") }; message .append (generic_gas_name) - .append ("), please use one of the ABI-prefixed names:") + .append (PSTR("), please use one of the ABI-prefixed names:")) .append (Constants::newline) - .append (" ").append (arm64_gas_name.data ()).append (Constants::newline) - .append (" ").append (arm32_gas_name.data ()).append (Constants::newline) - .append (" ").append (x86_gas_name.data ()).append (Constants::newline) - .append (" ").append (x64_gas_name.data ()).append (Constants::newline); + .append (PSTR(" ")).append (arm64_gas_name.data ()).append (Constants::newline) + .append (PSTR(" ")).append (arm32_gas_name.data ()).append (Constants::newline) + .append (PSTR(" ")).append (x86_gas_name.data ()).append (Constants::newline) + .append (PSTR(" ")).append (x64_gas_name.data ()).append (Constants::newline); return usage (true /* is_error */, message); } else { - std::string message { "Unknown program name '" }; - message.append (arch_name).append ("'").append (Constants::newline); + platform::string message { PSTR("Unknown program name '") }; + message + .append (arch_name) + .append (PSTR("'")) + .append (Constants::newline); return usage (true /* is_error */, message); } - auto&& [terminate, is_error] = parse_arguments (argc, argv, mc_runner); + auto&& [terminate, is_error] = parse_arguments (args, mc_runner); if (terminate || is_error) { return is_error ? Constants::wrapper_general_error_code : 0; } fs::path llvm_mc = program_dir () / Constants::llvm_mc_name; - bool multiple_input_files = false; bool derive_output_file_name = false; switch (input_files.size ()) { case 0: - return usage (true, "missing input files on command line"); + return usage (true, PSTR("missing input files on command line")); case 1: // We should always have a value here since `a.out` is the default, but... :) @@ -144,6 +158,7 @@ int Gas::run (int argc, char **argv) mc_runner->set_input_file_path (input, derive_output_file_name); int ret = mc_runner->run (llvm_mc); if (ret != 0) { + STDERR << " mc_runner failed with error code " << ret << Constants::newline; return ret; } } @@ -157,13 +172,11 @@ int Gas::run (int argc, char **argv) fs::path ld_path { program_dir () }; ld_path /= ld_name; auto ld = std::make_unique (ld_path); - ld->append_program_argument ("-o"); - ld->append_program_argument (_gas_output_file.empty () ? Constants::default_output_name : _gas_output_file.string ()); - ld->append_program_argument ("--relocatable"); + ld->append_program_argument (PSTR("-o")); + ld->append_program_argument (_gas_output_file.empty () ? platform::string (Constants::default_output_name) : _gas_output_file.string ()); + ld->append_program_argument (PSTR("--relocatable")); - std::cout << "Have multiple output files:" << Constants::newline; for (fs::path const& output : output_files) { - std::cout << " " << output << Constants::newline; ld->append_program_argument (output.string ()); } @@ -173,123 +186,110 @@ int Gas::run (int argc, char **argv) return 0; } -std::vector