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