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