1//===-- llvm-ml.cpp - masm-compatible assembler -----------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// A simple driver around MasmParser; based on llvm-mc.
10//
11//===----------------------------------------------------------------------===//
12
13#include "llvm/ADT/StringSwitch.h"
14#include "llvm/MC/MCAsmBackend.h"
15#include "llvm/MC/MCAsmInfo.h"
16#include "llvm/MC/MCCodeEmitter.h"
17#include "llvm/MC/MCContext.h"
18#include "llvm/MC/MCInstPrinter.h"
19#include "llvm/MC/MCInstrInfo.h"
20#include "llvm/MC/MCObjectFileInfo.h"
21#include "llvm/MC/MCObjectWriter.h"
22#include "llvm/MC/MCParser/AsmLexer.h"
23#include "llvm/MC/MCParser/MCTargetAsmParser.h"
24#include "llvm/MC/MCRegisterInfo.h"
25#include "llvm/MC/MCStreamer.h"
26#include "llvm/MC/MCSubtargetInfo.h"
27#include "llvm/MC/MCSymbol.h"
28#include "llvm/MC/MCTargetOptionsCommandFlags.h"
29#include "llvm/MC/TargetRegistry.h"
30#include "llvm/Option/Arg.h"
31#include "llvm/Option/ArgList.h"
32#include "llvm/Option/Option.h"
33#include "llvm/Support/Compression.h"
34#include "llvm/Support/FileUtilities.h"
35#include "llvm/Support/FormatVariadic.h"
36#include "llvm/Support/FormattedStream.h"
37#include "llvm/Support/LLVMDriver.h"
38#include "llvm/Support/MemoryBuffer.h"
39#include "llvm/Support/Path.h"
40#include "llvm/Support/Process.h"
41#include "llvm/Support/SourceMgr.h"
42#include "llvm/Support/TargetSelect.h"
43#include "llvm/Support/ToolOutputFile.h"
44#include "llvm/Support/VirtualFileSystem.h"
45#include "llvm/Support/WithColor.h"
46#include "llvm/TargetParser/Host.h"
47#include <ctime>
48#include <optional>
49
50using namespace llvm;
51using namespace llvm::opt;
52
53namespace {
54
55enum ID {
56 OPT_INVALID = 0, // This is not an option ID.
57#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
58#include "Opts.inc"
59#undef OPTION
60};
61
62#define OPTTABLE_STR_TABLE_CODE
63#include "Opts.inc"
64#undef OPTTABLE_STR_TABLE_CODE
65
66#define OPTTABLE_PREFIXES_TABLE_CODE
67#include "Opts.inc"
68#undef OPTTABLE_PREFIXES_TABLE_CODE
69
70static constexpr opt::OptTable::Info InfoTable[] = {
71#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
72#include "Opts.inc"
73#undef OPTION
74};
75
76class MLOptTable : public opt::GenericOptTable {
77public:
78 MLOptTable()
79 : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable,
80 /*IgnoreCase=*/false) {}
81};
82} // namespace
83
84static Triple GetTriple(StringRef ProgName, opt::InputArgList &Args) {
85 // Figure out the target triple.
86 StringRef DefaultBitness = "32";
87 SmallString<255> Program = ProgName;
88 sys::path::replace_extension(path&: Program, extension: "");
89 if (Program.ends_with(Suffix: "ml64"))
90 DefaultBitness = "64";
91
92 StringRef TripleName =
93 StringSwitch<StringRef>(Args.getLastArgValue(Id: OPT_bitness, Default: DefaultBitness))
94 .Case(S: "32", Value: "i386-pc-windows")
95 .Case(S: "64", Value: "x86_64-pc-windows")
96 .Default(Value: "");
97 return Triple(Triple::normalize(Str: TripleName));
98}
99
100static std::unique_ptr<ToolOutputFile> GetOutputStream(StringRef Path) {
101 std::error_code EC;
102 auto Out = std::make_unique<ToolOutputFile>(args&: Path, args&: EC, args: sys::fs::OF_None);
103 if (EC) {
104 WithColor::error() << EC.message() << '\n';
105 return nullptr;
106 }
107
108 return Out;
109}
110
111static int AsLexInput(SourceMgr &SrcMgr, MCAsmInfo &MAI, raw_ostream &OS) {
112 AsmLexer Lexer(MAI);
113 Lexer.setBuffer(Buf: SrcMgr.getMemoryBuffer(i: SrcMgr.getMainFileID())->getBuffer());
114 Lexer.setLexMasmIntegers(true);
115 Lexer.useMasmDefaultRadix(V: true);
116 Lexer.setLexMasmHexFloats(true);
117 Lexer.setLexMasmStrings(true);
118
119 bool Error = false;
120 while (Lexer.Lex().isNot(K: AsmToken::Eof)) {
121 Lexer.getTok().dump(OS);
122 OS << "\n";
123 if (Lexer.getTok().getKind() == AsmToken::Error)
124 Error = true;
125 }
126
127 return Error;
128}
129
130static int AssembleInput(StringRef ProgName, const Target *TheTarget,
131 SourceMgr &SrcMgr, MCContext &Ctx, MCStreamer &Str,
132 MCAsmInfo &MAI, MCSubtargetInfo &STI,
133 MCInstrInfo &MCII, MCTargetOptions &MCOptions,
134 const opt::ArgList &InputArgs) {
135 struct tm TM;
136 time_t Timestamp;
137 if (InputArgs.hasArg(Ids: OPT_timestamp)) {
138 StringRef TimestampStr = InputArgs.getLastArgValue(Id: OPT_timestamp);
139 int64_t IntTimestamp;
140 if (TimestampStr.getAsInteger(Radix: 10, Result&: IntTimestamp)) {
141 WithColor::error(OS&: errs(), Prefix: ProgName)
142 << "invalid timestamp '" << TimestampStr
143 << "'; must be expressed in seconds since the UNIX epoch.\n";
144 return 1;
145 }
146 Timestamp = IntTimestamp;
147 } else {
148 Timestamp = time(timer: nullptr);
149 }
150 if (InputArgs.hasArg(Ids: OPT_utc)) {
151 // Not thread-safe.
152 TM = *gmtime(timer: &Timestamp);
153 } else {
154 // Not thread-safe.
155 TM = *localtime(timer: &Timestamp);
156 }
157
158 std::unique_ptr<MCAsmParser> Parser(
159 createMCMasmParser(SrcMgr, Ctx, Str, MAI, TM, CB: 0));
160 std::unique_ptr<MCTargetAsmParser> TAP(
161 TheTarget->createMCAsmParser(STI, Parser&: *Parser, MII: MCII, Options: MCOptions));
162
163 if (!TAP) {
164 WithColor::error(OS&: errs(), Prefix: ProgName)
165 << "this target does not support assembly parsing.\n";
166 return 1;
167 }
168
169 Parser->setShowParsedOperands(InputArgs.hasArg(Ids: OPT_show_inst_operands));
170 Parser->setTargetParser(*TAP);
171 Parser->getLexer().setLexMasmIntegers(true);
172 Parser->getLexer().useMasmDefaultRadix(V: true);
173 Parser->getLexer().setLexMasmHexFloats(true);
174 Parser->getLexer().setLexMasmStrings(true);
175
176 auto Defines = InputArgs.getAllArgValues(Id: OPT_define);
177 for (StringRef Define : Defines) {
178 const auto NameValue = Define.split(Separator: '=');
179 StringRef Name = NameValue.first, Value = NameValue.second;
180 if (Parser->defineMacro(Name, Value)) {
181 WithColor::error(OS&: errs(), Prefix: ProgName)
182 << "can't define macro '" << Name << "' = '" << Value << "'\n";
183 return 1;
184 }
185 }
186
187 int Res = Parser->Run(/*NoInitialTextSection=*/true);
188
189 return Res;
190}
191
192int llvm_ml_main(int Argc, char **Argv, const llvm::ToolContext &) {
193 StringRef ProgName = sys::path::filename(path: Argv[0]);
194
195 // Initialize targets and assembly printers/parsers.
196 llvm::InitializeAllTargetInfos();
197 llvm::InitializeAllTargetMCs();
198 llvm::InitializeAllAsmParsers();
199 llvm::InitializeAllDisassemblers();
200
201 MLOptTable T;
202 unsigned MissingArgIndex, MissingArgCount;
203 ArrayRef<const char *> ArgsArr = ArrayRef(Argv + 1, Argc - 1);
204 opt::InputArgList InputArgs =
205 T.ParseArgs(Args: ArgsArr, MissingArgIndex, MissingArgCount);
206
207 std::string InputFilename;
208 for (auto *Arg : InputArgs.filtered(Ids: OPT_INPUT)) {
209 std::string ArgString = Arg->getAsString(Args: InputArgs);
210 bool IsFile = false;
211 std::error_code IsFileEC =
212 llvm::sys::fs::is_regular_file(path: ArgString, result&: IsFile);
213 if (ArgString == "-" || IsFile) {
214 if (!InputFilename.empty()) {
215 WithColor::warning(OS&: errs(), Prefix: ProgName)
216 << "does not support multiple assembly files in one command; "
217 << "ignoring '" << InputFilename << "'\n";
218 }
219 InputFilename = ArgString;
220 } else {
221 std::string Diag;
222 raw_string_ostream OS(Diag);
223 OS << ArgString << ": " << IsFileEC.message();
224
225 std::string Nearest;
226 if (T.findNearest(Option: ArgString, NearestString&: Nearest) < 2)
227 OS << ", did you mean '" << Nearest << "'?";
228
229 WithColor::error(OS&: errs(), Prefix: ProgName) << OS.str() << '\n';
230 exit(status: 1);
231 }
232 }
233 for (auto *Arg : InputArgs.filtered(Ids: OPT_assembly_file)) {
234 if (!InputFilename.empty()) {
235 WithColor::warning(OS&: errs(), Prefix: ProgName)
236 << "does not support multiple assembly files in one command; "
237 << "ignoring '" << InputFilename << "'\n";
238 }
239 InputFilename = Arg->getValue();
240 }
241
242 for (auto *Arg : InputArgs.filtered(Ids: OPT_unsupported_Group)) {
243 WithColor::warning(OS&: errs(), Prefix: ProgName)
244 << "ignoring unsupported '" << Arg->getOption().getName()
245 << "' option\n";
246 }
247
248 if (InputArgs.hasArg(Ids: OPT_debug)) {
249 DebugFlag = true;
250 }
251 for (auto *Arg : InputArgs.filtered(Ids: OPT_debug_only)) {
252 setCurrentDebugTypes(Arg->getValues().data(), Arg->getNumValues());
253 }
254
255 if (InputArgs.hasArg(Ids: OPT_help)) {
256 std::string Usage = llvm::formatv(Fmt: "{0} [ /options ] file", Vals&: ProgName).str();
257 T.printHelp(OS&: outs(), Usage: Usage.c_str(), Title: "LLVM MASM Assembler",
258 /*ShowHidden=*/false);
259 return 0;
260 } else if (InputFilename.empty()) {
261 outs() << "USAGE: " << ProgName << " [ /options ] file\n"
262 << "Run \"" << ProgName << " /?\" or \"" << ProgName
263 << " /help\" for more info.\n";
264 return 0;
265 }
266
267 MCTargetOptions MCOptions;
268 MCOptions.AssemblyLanguage = "masm";
269 MCOptions.MCFatalWarnings = InputArgs.hasArg(Ids: OPT_fatal_warnings);
270 MCOptions.MCSaveTempLabels = InputArgs.hasArg(Ids: OPT_save_temp_labels);
271 MCOptions.ShowMCInst = InputArgs.hasArg(Ids: OPT_show_inst);
272 MCOptions.AsmVerbose = true;
273
274 Triple TheTriple = GetTriple(ProgName, Args&: InputArgs);
275 std::string Error;
276 const Target *TheTarget = TargetRegistry::lookupTarget(ArchName: "", TheTriple, Error);
277 if (!TheTarget) {
278 WithColor::error(OS&: errs(), Prefix: ProgName) << Error;
279 return 1;
280 }
281 bool SafeSEH = InputArgs.hasArg(Ids: OPT_safeseh);
282 if (SafeSEH && !(TheTriple.isArch32Bit() && TheTriple.isX86())) {
283 WithColor::warning()
284 << "/safeseh applies only to 32-bit X86 platforms; ignoring.\n";
285 SafeSEH = false;
286 }
287
288 ErrorOr<std::unique_ptr<MemoryBuffer>> BufferPtr =
289 MemoryBuffer::getFileOrSTDIN(Filename: InputFilename);
290 if (std::error_code EC = BufferPtr.getError()) {
291 WithColor::error(OS&: errs(), Prefix: ProgName)
292 << InputFilename << ": " << EC.message() << '\n';
293 return 1;
294 }
295
296 SourceMgr SrcMgr;
297
298 // Tell SrcMgr about this buffer, which is what the parser will pick up.
299 SrcMgr.AddNewSourceBuffer(F: std::move(*BufferPtr), IncludeLoc: SMLoc());
300
301 // Record the location of the include directories so that the lexer can find
302 // included files later.
303 std::vector<std::string> IncludeDirs =
304 InputArgs.getAllArgValues(Id: OPT_include_path);
305 if (!InputArgs.hasArg(Ids: OPT_ignore_include_envvar)) {
306 if (std::optional<std::string> IncludeEnvVar =
307 llvm::sys::Process::GetEnv(name: "INCLUDE")) {
308 SmallVector<StringRef, 8> Dirs;
309 StringRef(*IncludeEnvVar)
310 .split(A&: Dirs, Separator: ";", /*MaxSplit=*/-1, /*KeepEmpty=*/false);
311 IncludeDirs.reserve(n: IncludeDirs.size() + Dirs.size());
312 for (StringRef Dir : Dirs)
313 IncludeDirs.push_back(x: Dir.str());
314 }
315 }
316 SrcMgr.setIncludeDirs(IncludeDirs);
317 SrcMgr.setVirtualFileSystem(vfs::getRealFileSystem());
318
319 std::unique_ptr<MCRegisterInfo> MRI(TheTarget->createMCRegInfo(TT: TheTriple));
320 assert(MRI && "Unable to create target register info!");
321
322 std::unique_ptr<MCAsmInfo> MAI(
323 TheTarget->createMCAsmInfo(MRI: *MRI, TheTriple, Options: MCOptions));
324 assert(MAI && "Unable to create target asm info!");
325
326 MAI->setPreserveAsmComments(InputArgs.hasArg(Ids: OPT_preserve_comments));
327
328 std::unique_ptr<MCSubtargetInfo> STI(
329 TheTarget->createMCSubtargetInfo(TheTriple, /*CPU=*/"", /*Features=*/""));
330 if (!STI) {
331 WithColor::error(OS&: errs(), Prefix: ProgName) << "unable to create subtarget info\n";
332 exit(status: 1);
333 }
334
335 // FIXME: This is not pretty. MCContext has a ptr to MCObjectFileInfo and
336 // MCObjectFileInfo needs a MCContext reference in order to initialize itself.
337 MCContext Ctx(TheTriple, MAI.get(), MRI.get(), STI.get(), &SrcMgr);
338 std::unique_ptr<MCObjectFileInfo> MOFI(TheTarget->createMCObjectFileInfo(
339 Ctx, /*PIC=*/false, /*LargeCodeModel=*/true));
340 Ctx.setObjectFileInfo(MOFI.get());
341
342 // Set compilation information.
343 SmallString<128> CWD;
344 if (!sys::fs::current_path(result&: CWD))
345 Ctx.setCompilationDir(CWD);
346 Ctx.setMainFileName(InputFilename);
347
348 StringRef FileType = InputArgs.getLastArgValue(Id: OPT_filetype, Default: "obj");
349 SmallString<255> DefaultOutputFilename;
350 if (InputArgs.hasArg(Ids: OPT_as_lex)) {
351 DefaultOutputFilename = "-";
352 } else {
353 DefaultOutputFilename = InputFilename;
354 sys::path::replace_extension(path&: DefaultOutputFilename, extension: FileType);
355 }
356 const StringRef OutputFilename =
357 InputArgs.getLastArgValue(Id: OPT_output_file, Default: DefaultOutputFilename);
358 std::unique_ptr<ToolOutputFile> Out = GetOutputStream(Path: OutputFilename);
359 if (!Out)
360 return 1;
361
362 std::unique_ptr<buffer_ostream> BOS;
363 raw_pwrite_stream *OS = &Out->os();
364 std::unique_ptr<MCStreamer> Str;
365
366 std::unique_ptr<MCInstrInfo> MCII(TheTarget->createMCInstrInfo());
367 assert(MCII && "Unable to create instruction info!");
368
369 if (FileType == "s") {
370 const bool OutputATTAsm = InputArgs.hasArg(Ids: OPT_output_att_asm);
371 const unsigned OutputAsmVariant = OutputATTAsm ? 0U // ATT dialect
372 : 1U; // Intel dialect
373 std::unique_ptr<MCInstPrinter> IP(TheTarget->createMCInstPrinter(
374 T: TheTriple, SyntaxVariant: OutputAsmVariant, MAI: *MAI, MII: *MCII, MRI: *MRI));
375
376 if (!IP) {
377 WithColor::error()
378 << "unable to create instruction printer for target triple '"
379 << TheTriple.normalize() << "' with "
380 << (OutputATTAsm ? "ATT" : "Intel") << " assembly variant.\n";
381 return 1;
382 }
383
384 // Set the display preference for hex vs. decimal immediates.
385 IP->setPrintImmHex(InputArgs.hasArg(Ids: OPT_print_imm_hex));
386
387 // Set up the AsmStreamer.
388 std::unique_ptr<MCCodeEmitter> CE;
389 if (InputArgs.hasArg(Ids: OPT_show_encoding))
390 CE.reset(p: TheTarget->createMCCodeEmitter(II: *MCII, Ctx));
391
392 std::unique_ptr<MCAsmBackend> MAB(
393 TheTarget->createMCAsmBackend(STI: *STI, MRI: *MRI, Options: MCOptions));
394 auto FOut = std::make_unique<formatted_raw_ostream>(args&: *OS);
395 Str.reset(p: TheTarget->createAsmStreamer(Ctx, OS: std::move(FOut), IP: std::move(IP),
396 CE: std::move(CE), TAB: std::move(MAB)));
397
398 } else if (FileType == "null") {
399 Str.reset(p: TheTarget->createNullStreamer(Ctx));
400 } else if (FileType == "obj") {
401 if (!Out->os().supportsSeeking()) {
402 BOS = std::make_unique<buffer_ostream>(args&: Out->os());
403 OS = BOS.get();
404 }
405
406 MCCodeEmitter *CE = TheTarget->createMCCodeEmitter(II: *MCII, Ctx);
407 MCAsmBackend *MAB = TheTarget->createMCAsmBackend(STI: *STI, MRI: *MRI, Options: MCOptions);
408 Str.reset(p: TheTarget->createMCObjectStreamer(
409 T: TheTriple, Ctx, TAB: std::unique_ptr<MCAsmBackend>(MAB),
410 OW: MAB->createObjectWriter(OS&: *OS), Emitter: std::unique_ptr<MCCodeEmitter>(CE),
411 STI: *STI));
412 } else {
413 llvm_unreachable("Invalid file type!");
414 }
415
416 if (TheTriple.isOSBinFormatCOFF()) {
417 // Emit an absolute @feat.00 symbol. This is a features bitfield read by
418 // link.exe.
419 int64_t Feat00Flags = 0x2;
420 if (SafeSEH) {
421 // According to the PE-COFF spec, the LSB of this value marks the object
422 // for "registered SEH". This means that all SEH handler entry points
423 // must be registered in .sxdata. Use of any unregistered handlers will
424 // cause the process to terminate immediately.
425 Feat00Flags |= 0x1;
426 }
427 MCSymbol *Feat00Sym = Ctx.getOrCreateSymbol(Name: "@feat.00");
428 Feat00Sym->setRedefinable(true);
429 Str->emitSymbolAttribute(Symbol: Feat00Sym, Attribute: MCSA_Global);
430 Str->emitAssignment(Symbol: Feat00Sym, Value: MCConstantExpr::create(Value: Feat00Flags, Ctx));
431 }
432
433 int Res = 1;
434 if (InputArgs.hasArg(Ids: OPT_as_lex)) {
435 // -as-lex; Lex only, and output a stream of tokens
436 Res = AsLexInput(SrcMgr, MAI&: *MAI, OS&: Out->os());
437 } else {
438 Res = AssembleInput(ProgName, TheTarget, SrcMgr, Ctx, Str&: *Str, MAI&: *MAI, STI&: *STI,
439 MCII&: *MCII, MCOptions, InputArgs);
440 }
441
442 // Keep output if no errors.
443 if (Res == 0)
444 Out->keep();
445 return Res;
446}
447