[libc] Add a TableGen based header generator.

Summary:
* The Python header generator has been removed.
* Docs giving a highlevel overview of the header gen scheme have been
  added.

Reviewers: phosek, abrachet

Subscribers: mgorny, MaskRay, tschuett, libc-commits

Tags: #libc-project

Differential Revision: https://reviews.llvm.org/D70197
This commit is contained in:
Siva Chandra Reddy 2019-11-05 11:40:26 -08:00
parent a6150b48ce
commit b47f9eb55d
24 changed files with 1107 additions and 262 deletions

View File

@ -22,3 +22,4 @@ include(LLVMLibCRules)
add_subdirectory(include)
add_subdirectory(src)
add_subdirectory(lib)
add_subdirectory(utils)

View File

@ -54,7 +54,7 @@ function(add_gen_header target_name)
"ADD_GEN_HDR"
"" # No optional arguments
"DEF_FILE;GEN_HDR" # Single value arguments
"PARAMS;DATA_FILES" # Multi value arguments
"PARAMS;DATA_FILES;DEPENDS" # Multi value arguments
${ARGN}
)
if(NOT ADD_GEN_HDR_DEF_FILE)
@ -76,21 +76,21 @@ function(add_gen_header target_name)
set(replacement_params "")
if(ADD_GEN_HDR_PARAMS)
list(APPEND replacement_params "-P" ${ADD_GEN_HDR_PARAMS})
list(APPEND replacement_params "--args" ${ADD_GEN_HDR_PARAMS})
endif()
set(gen_hdr_script "${LIBC_BUILD_SCRIPTS_DIR}/gen_hdr.py")
add_custom_command(
OUTPUT ${out_file}
COMMAND ${gen_hdr_script} -o ${out_file} ${in_file} ${replacement_params}
COMMAND $<TARGET_FILE:libc-hdrgen> -o ${out_file} --header ${ADD_GEN_HDR_GEN_HDR} --def ${in_file} ${replacement_params} -I ${LIBC_SOURCE_DIR} ${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/api.td
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${in_file} ${fq_data_files} ${gen_hdr_script}
DEPENDS ${in_file} ${fq_data_files} ${LIBC_SOURCE_DIR}/config/${LIBC_TARGET_OS}/api.td libc-hdrgen
)
add_custom_target(
${target_name}
DEPENDS ${out_file}
DEPENDS ${out_file} ${ADD_GEN_HDR_DEPENDS}
)
endfunction(add_gen_header)

85
libc/config/linux/api.td Normal file
View File

@ -0,0 +1,85 @@
include "config/public_api.td"
include "spec/stdc.td"
def FloatT : TypeDecl<"float_t"> {
let Decl = [{
#if __FLT_EVAL_METHOD__ == 1
typedef float float_t
#elif __FLT_EVAL_METHOD__ == 2
...
#else
...
#endif
}]; // This is only an example and not exactly how it will appear
}
def SizeT : TypeDecl<"size_t"> {
let Decl = [{
#define __need_size_t
#include <stddef.h>
}];
}
def NullMacro : MacroDef<"NULL"> {
let Defn = [{
#define __need_NULL
#include <stddef.h>
}];
}
def MathAPI : PublicAPI<"math.h"> {
let Functions = [
"acos",
"acosl",
];
let TypeDeclarations = [
FloatT,
];
}
def StringAPI : PublicAPI<"string.h"> {
let Functions = [
"memcpy",
"memmove",
"memcmp",
"memchr",
"memset",
"strcpy",
"strncpy",
"strcat",
"strncat",
"strcmp",
"strcoll",
"strncmp",
"strxfrm",
"strchr",
"strcspn",
"strpbrk",
"strrchr",
"strspn",
"strstr",
"strtok",
"strerror",
"strlen",
];
let TypeDeclarations = [
SizeT,
];
let Macros = [
NullMacro,
];
}
def StdIOAPI : PublicAPI<"stdio.h"> {
let TypeDeclarations = [
SizeT,
];
let Functions = [
"snprintf",
];
}

19
libc/config/public_api.td Normal file
View File

@ -0,0 +1,19 @@
include "spec/spec.td"
class TypeDecl<string name> {
string Name = name;
string Decl = "";
}
class MacroDef<string name> {
string Name = name;
string Defn = "";
}
class PublicAPI<string name> {
string HeaderName = name;
list<MacroDef> Macros = [];
list<TypeDecl> TypeDeclarations = [];
list<string> Structs = [];
list<string> Functions = [];
}

View File

@ -0,0 +1,11 @@
The ground truth of standards
=============================
Like any modern libc, LLVM libc also supports a wide number of standards and
extensions. To avoid developing headers, wrappers and sources in a disjointed
fashion, LLVM libc employs ground truth files. These files live under the
``spec`` directory and list ground truth corresponding the ISO C standard, the
POSIX extension standard, etc. For example, the path to the ground truth file
for the ISO C standard is ``spec/stdc.td``. Tools like the header generator
(described in the header generation document), docs generator, etc. use the
ground truth files to generate headers, docs etc.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 518 KiB

View File

@ -96,3 +96,21 @@ Action
The header generator will only include content starting from the line after the
line on which this command is listed.
``public_api``
~~~~~~~~~~~~~~
This is a replacement command which should be listed in an input ``.h.def``
file. The header file generator will replace this command with the public API of
the target platform. See the build system document for more information on the
relevant build rules. Also, see "Mechanics of public_api" to learn the mechanics
of how the header generator replaces this command with the public API.
Arguments
None.
Action
The header generator will replace this command with the public API to be exposed
from the generated header file.

View File

@ -0,0 +1,29 @@
The mechanics of the ``public_api`` command
===========================================
The build system, in combination with the header generation mechanism,
facilitates the fine grained ability to pick and choose the public API one wants
to expose on their platform. The public header files are always generated from
the corresponding ``.h.def`` files. A header generation command ``%%public_api``
is listed in these files. In the generated header file, the header generator
replaces this command with the public API relevant for the target platform.
Under the hood
--------------
When the header generator sees the ``%%public_api`` command, it looks up the
API config file for the platform in the path ``config/<platform>/api.td``.
The API config file lists two kinds of items:
1. The list of standards from which the public entities available on the platform
are derived from.
2. For each header file exposed on the platfrom, the list of public members
provided in that header file.
Note that, the header generator only learns the names of the public entities
from the header config file (the 2nd item from above.) The exact manner in which
the entities are to be declared is got from the standards (the 1st item from
above.)
See the ground truth document for more information on how the standards are
formally listed in LLVM libc using LLVM table-gen files.

View File

@ -21,10 +21,10 @@ add_header(
llvm_libc_common_h
)
add_header(
add_gen_header(
string_h
HDR
string.h
DEF_FILE string.h.def
GEN_HDR string.h
DEPENDS
llvm_libc_common_h
)

View File

@ -1,66 +0,0 @@
//===---------------- C standard library header string.h ------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_STRING_H
#define LLVM_LIBC_STRING_H
#include <__llvm-libc-common.h>
#define __need_size_t // To get only size_t from stddef.h
#define __need_NULL // To get only NULL from stddef.h
#include <stddef.h>
__BEGIN_C_DECLS
void *memcpy(void *__restrict, const void *__restrict, size_t);
void *memmove(void *, const void *, size_t);
int memcmp(const void *, const void *, size_t);
void *memchr(const void *, int, size_t);
void *memset(void *, int, size_t);
char *strcpy(char *__restrict, const char *__restrict);
char *strncpy(char *__restrict, const char *__restrict, size_t);
char *strcat(char *__restrict, const char *__restrict);
char *strncat(char *, const char *, size_t);
int strcmp(const char *, const char *);
int strcoll(const char *, const char *);
int strncmp(const char *, const char *, size_t);
size_t strxfrm(char *__restrict, const char *__restrict, size_t);
char *strchr(const char *, int);
size_t strcspn(const char *, const char *);
char *strpbrk(const char *, const char *);
char *strrchr(const char *, int c);
size_t strspn(const char *, const char *);
char *strstr(const char *, const char *);
char *strtok(char *__restrict, const char *__restrict);
char *strerror(int);
size_t strlen(const char *);
__END_C_DECLS
#endif // LLVM_LIBC_STRING_H

16
libc/include/string.h.def Normal file
View File

@ -0,0 +1,16 @@
//===---------------- C standard library header string.h ------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_STRING_H
#define LLVM_LIBC_STRING_H
#include <__llvm-libc-common.h>
%%public_api()
#endif // LLVM_LIBC_STRING_H

74
libc/spec/spec.td Normal file
View File

@ -0,0 +1,74 @@
class Type {}
class NamedType<string name> : Type {
string Name = name;
}
class Field<string name, Type type> {
string Name = name;
Type FieldType = type;
}
// Class to describe concrete structs specified by a standard.
class Struct<string name> : NamedType<name> {
list<Field> Fields;
}
class PtrType<Type type> : Type {
Type PointeeType = type;
}
class ConstType<Type type> : Type {
Type UnqualifiedType = type;
}
class RestrictedPtrType<Type type> : Type {
Type PointeeType = type;
}
// Builtin types.
def VarArgType : Type {}
def VoidType : NamedType<"void">;
def IntType : NamedType<"int">;
def FloatType : NamedType<"float">;
def DoubleType : NamedType<"double">;
def LongDoubleType : NamedType<"long double">;
def CharType : NamedType<"char">;
class Macro<string name> {
string Name = name;
}
class Annotation {}
class RetValSpec<Type type, list<Annotation> annotations = []> {
Type ReturnType = type;
list<Annotation> Annotations = annotations;
}
class ArgSpec<Type type, list<Annotation> annotations = [], string name = ""> {
Type ArgType = type;
list<Annotation> Annotations = annotations;
string Name = name;
}
class FunctionSpec<string name, RetValSpec return, list<ArgSpec> args> {
string Name = name;
RetValSpec Return = return;
list<ArgSpec> Args = args;
}
class HeaderSpec<string name,
list<Macro> macros,
list<Type> types,
list<FunctionSpec> functions> {
string Name = name;
list<FunctionSpec> Functions = functions;
list<Type> Types = types;
list<Macro> Macros = macros;
}
class StandardSpec<string name> {
string Name = name;
list<HeaderSpec> Headers;
}

178
libc/spec/stdc.td Normal file
View File

@ -0,0 +1,178 @@
def StdC : StandardSpec<"stdc"> {
PtrType VoidPtr = PtrType<VoidType>;
ConstType ConstVoidPtr = ConstType<VoidPtr>;
RestrictedPtrType VoidRestrictedPtr = RestrictedPtrType<VoidType>;
ConstType ConstVoidRestrictedPtr = ConstType<VoidRestrictedPtr>;
PtrType CharPtr = PtrType<CharType>;
ConstType ConstCharPtr = ConstType<CharPtr>;
RestrictedPtrType CharRestrictedPtr = RestrictedPtrType<CharType>;
ConstType ConstCharRestrictedPtr = ConstType<CharRestrictedPtr>;
NamedType SizeTType = NamedType<"size_t">;
HeaderSpec String = HeaderSpec<
"string.h",
[
Macro<"NULL">,
],
[
SizeTType,
],
[
FunctionSpec<
"memcpy",
RetValSpec<VoidPtr>,
[ArgSpec<VoidRestrictedPtr>,
ArgSpec<ConstVoidRestrictedPtr>,
ArgSpec<SizeTType>]
>,
FunctionSpec<
"memmove",
RetValSpec<VoidPtr>,
[ArgSpec<VoidPtr>, ArgSpec<ConstVoidPtr>, ArgSpec<SizeTType>]
>,
FunctionSpec<
"memcmp",
RetValSpec<IntType>,
[ArgSpec<ConstVoidPtr>, ArgSpec<ConstVoidPtr>, ArgSpec<SizeTType>]
>,
FunctionSpec<
"memchr",
RetValSpec<VoidPtr>,
[ArgSpec<ConstVoidPtr>, ArgSpec<IntType>, ArgSpec<SizeTType>]
>,
FunctionSpec<
"memset",
RetValSpec<VoidPtr>,
[ArgSpec<VoidPtr>, ArgSpec<IntType>, ArgSpec<SizeTType>]
>,
FunctionSpec<
"strcpy",
RetValSpec<CharPtr>,
[ArgSpec<CharRestrictedPtr>, ArgSpec<ConstCharRestrictedPtr>]
>,
FunctionSpec<
"strncpy",
RetValSpec<CharPtr>,
[ArgSpec<CharRestrictedPtr>,
ArgSpec<ConstCharRestrictedPtr>,
ArgSpec<SizeTType>]
>,
FunctionSpec<
"strcat",
RetValSpec<CharPtr>,
[ArgSpec<CharRestrictedPtr>, ArgSpec<ConstCharRestrictedPtr>]
>,
FunctionSpec<
"strncat",
RetValSpec<CharPtr>,
[ArgSpec<CharPtr>, ArgSpec<ConstCharPtr>, ArgSpec<SizeTType>]
>,
FunctionSpec<
"strcmp",
RetValSpec<IntType>,
[ArgSpec<ConstCharPtr>, ArgSpec<ConstCharPtr>]
>,
FunctionSpec<
"strcoll",
RetValSpec<IntType>,
[ArgSpec<ConstCharPtr>, ArgSpec<ConstCharPtr>]
>,
FunctionSpec<
"strncmp",
RetValSpec<IntType>,
[ArgSpec<ConstCharPtr>, ArgSpec<ConstCharPtr>, ArgSpec<SizeTType>]
>,
FunctionSpec<
"strxfrm",
RetValSpec<SizeTType>,
[ArgSpec<CharRestrictedPtr>,
ArgSpec<ConstCharRestrictedPtr>,
ArgSpec<SizeTType>]
>,
FunctionSpec<
"strchr",
RetValSpec<CharPtr>,
[ArgSpec<ConstCharPtr>, ArgSpec<IntType>]
>,
FunctionSpec<
"strcspn",
RetValSpec<SizeTType>,
[ArgSpec<ConstCharPtr>, ArgSpec<ConstCharPtr>]
>,
FunctionSpec<
"strpbrk",
RetValSpec<CharPtr>,
[ArgSpec<ConstCharPtr>, ArgSpec<ConstCharPtr>]
>,
FunctionSpec<
"strrchr",
RetValSpec<CharPtr>,
[ArgSpec<ConstCharPtr>, ArgSpec<IntType>]
>,
FunctionSpec<
"strspn",
RetValSpec<SizeTType>,
[ArgSpec<ConstCharPtr>, ArgSpec<ConstCharPtr>]
>,
FunctionSpec<
"strstr",
RetValSpec<CharPtr>,
[ArgSpec<ConstCharPtr>, ArgSpec<ConstCharPtr>]
>,
FunctionSpec<
"strtok",
RetValSpec<CharPtr>,
[ArgSpec<CharRestrictedPtr>, ArgSpec<ConstCharRestrictedPtr>]
>,
FunctionSpec<
"strerror",
RetValSpec<CharPtr>,
[ArgSpec<IntType>]
>,
FunctionSpec<
"strlen",
RetValSpec<SizeTType>,
[ArgSpec<ConstCharPtr>]
>,
]
>;
HeaderSpec Math = HeaderSpec<
"math.h",
[], // Macros
[
NamedType<"float_t">,
NamedType<"double_t">,
],
[
FunctionSpec<"acos", RetValSpec<DoubleType>, [ArgSpec<DoubleType>]>,
FunctionSpec<"acosl", RetValSpec<LongDoubleType>, [ArgSpec<LongDoubleType>]>,
]
>;
HeaderSpec StdIO = HeaderSpec<
"stdio.h",
[], // Macros
[ // Types
SizeTType,
],
[
FunctionSpec<
"snprintf",
RetValSpec<IntType>,
[ArgSpec<CharPtr>,
ArgSpec<SizeTType>,
ArgSpec<ConstCharRestrictedPtr>,
ArgSpec<VarArgType>]
>,
]
>;
let Headers = [
Math,
String,
StdIO,
];
}

View File

@ -0,0 +1 @@
add_subdirectory(HdrGen)

View File

@ -0,0 +1,10 @@
add_tablegen(libc-hdrgen llvm-libc
Command.h
Generator.cpp
Generator.h
IncludeFileCommand.cpp
IncludeFileCommand.h
Main.cpp
PublicAPICommand.cpp
PublicAPICommand.h
)

View File

@ -0,0 +1,52 @@
//===-------- Base class for header generation commands ---------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_UTILS_HDRGEN_COMMAND_H
#define LLVM_LIBC_UTILS_HDRGEN_COMMAND_H
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/SourceMgr.h"
#include <cstdlib>
namespace llvm {
class raw_ostream;
class RecordKeeper;
} // namespace llvm
namespace llvm_libc {
typedef llvm::SmallVector<llvm::StringRef, 4> ArgVector;
class Command {
public:
class ErrorReporter {
llvm::SMLoc Loc;
const llvm::SourceMgr &SrcMgr;
public:
ErrorReporter(llvm::SMLoc L, llvm::SourceMgr &SM) : Loc(L), SrcMgr(SM) {}
void printFatalError(llvm::Twine Msg) const {
SrcMgr.PrintMessage(Loc, llvm::SourceMgr::DK_Error, Msg);
std::exit(1);
}
};
virtual void run(llvm::raw_ostream &OS, const ArgVector &Args,
llvm::StringRef StdHeader, llvm::RecordKeeper &Records,
const ErrorReporter &Reporter) const = 0;
};
} // namespace llvm_libc
#endif // LLVM_LIBC_UTILS_HDRGEN_COMMAND_H

View File

@ -0,0 +1,119 @@
//===---- Implementation of the main header generation class -----*- C++ -*===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "Generator.h"
#include "IncludeFileCommand.h"
#include "PublicAPICommand.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/raw_ostream.h"
#include <cstdlib>
#include <memory>
static const char CommandPrefix[] = "%%";
static const size_t CommandPrefixSize = llvm::StringRef(CommandPrefix).size();
static const char CommentPrefix[] = "<!>";
static const char ParamNamePrefix[] = "${";
static const size_t ParamNamePrefixSize =
llvm::StringRef(ParamNamePrefix).size();
static const char ParamNameSuffix[] = "}";
static const size_t ParamNameSuffixSize =
llvm::StringRef(ParamNameSuffix).size();
namespace llvm_libc {
Command *Generator::getCommandHandler(llvm::StringRef CommandName) {
if (CommandName == IncludeFileCommand::Name) {
if (!IncludeFileCmd)
IncludeFileCmd = std::make_unique<IncludeFileCommand>();
return IncludeFileCmd.get();
} else if (CommandName == PublicAPICommand::Name) {
if (!PublicAPICmd)
PublicAPICmd = std::make_unique<PublicAPICommand>();
return PublicAPICmd.get();
} else {
return nullptr;
}
}
void Generator::parseCommandArgs(llvm::StringRef ArgStr, ArgVector &Args) {
if (!ArgStr.contains(',') && ArgStr.trim(' ').trim('\t').size() == 0) {
// If it is just space between the parenthesis
return;
}
ArgStr.split(Args, ",");
for (llvm::StringRef &A : Args) {
A = A.trim(' ');
if (A.startswith(ParamNamePrefix) && A.endswith(ParamNameSuffix)) {
A = A.drop_front(ParamNamePrefixSize).drop_back(ParamNameSuffixSize);
A = ArgMap[A];
}
}
}
void Generator::generate(llvm::raw_ostream &OS, llvm::RecordKeeper &Records) {
auto DefFileBuffer = llvm::MemoryBuffer::getFile(HeaderDefFile);
if (!DefFileBuffer) {
llvm::errs() << "Unable to open " << HeaderDefFile << ".\n";
std::exit(1);
}
llvm::SourceMgr SrcMgr;
unsigned DefFileID = SrcMgr.AddNewSourceBuffer(
std::move(DefFileBuffer.get()), llvm::SMLoc::getFromPointer(nullptr));
llvm::StringRef Content = SrcMgr.getMemoryBuffer(DefFileID)->getBuffer();
while (true) {
std::pair<llvm::StringRef, llvm::StringRef> P = Content.split('\n');
Content = P.second;
llvm::StringRef Line = P.first.trim(' ');
if (Line.startswith(CommandPrefix)) {
Line = Line.drop_front(CommandPrefixSize);
P = Line.split("(");
if (P.second.empty() || P.second[P.second.size() - 1] != ')') {
SrcMgr.PrintMessage(llvm::SMLoc::getFromPointer(P.second.data()),
llvm::SourceMgr::DK_Error,
"Command argument list should begin with '(' "
"and end with ')'.");
std::exit(1);
}
llvm::StringRef CommandName = P.first;
Command *Cmd = getCommandHandler(CommandName);
if (Cmd == nullptr) {
SrcMgr.PrintMessage(llvm::SMLoc::getFromPointer(CommandName.data()),
llvm::SourceMgr::DK_Error,
"Unknown command '%%" + CommandName + "'.");
std::exit(1);
}
llvm::StringRef ArgStr = P.second.drop_back(1);
ArgVector Args;
parseCommandArgs(ArgStr, Args);
Command::ErrorReporter Reporter(
llvm::SMLoc::getFromPointer(CommandName.data()), SrcMgr);
Cmd->run(OS, Args, StdHeader, Records, Reporter);
} else if (!Line.startswith(CommentPrefix)) {
// There is no comment or command on this line so we just write it as is.
OS << P.first << "\n";
}
if (P.second.empty())
break;
}
}
} // namespace llvm_libc

View File

@ -0,0 +1,56 @@
//===------------- - The main header generation class -----------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_UTILS_HDRGEN_GENERATOR_H
#define LLVM_LIBC_UTILS_HDRGEN_GENERATOR_H
#include "Command.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include <memory>
#include <string>
#include <unordered_map>
namespace llvm {
class raw_ostream;
class RecordKeeper;
} // namespace llvm
namespace llvm_libc {
class Command;
class Generator {
llvm::StringRef HeaderDefFile;
llvm::StringRef StdHeader;
std::unordered_map<std::string, std::string> &ArgMap;
std::unique_ptr<Command> IncludeFileCmd;
std::unique_ptr<Command> PublicAPICmd;
Command *getCommandHandler(llvm::StringRef CommandName);
void parseCommandArgs(llvm::StringRef ArgStr, ArgVector &Args);
void printError(llvm::StringRef Msg);
public:
Generator(const std::string &DefFile, const std::string &Header,
std::unordered_map<std::string, std::string> &Map)
: HeaderDefFile(DefFile), StdHeader(Header), ArgMap(Map) {}
void generate(llvm::raw_ostream &OS, llvm::RecordKeeper &Records);
};
} // namespace llvm_libc
#endif // LLVM_LIBC_UTILS_HDRGEN_GENERATOR_H

View File

@ -0,0 +1,50 @@
//===----------- Implementation of IncludeFileCommand -----------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "IncludeFileCommand.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/SourceMgr.h"
#include <cstdlib>
namespace llvm_libc {
const char IncludeFileCommand::Name[] = "include_file";
void IncludeFileCommand::run(llvm::raw_ostream &OS, const ArgVector &Args,
llvm::StringRef StdHeader,
llvm::RecordKeeper &Records,
const Command::ErrorReporter &Reporter) const {
if (Args.size() != 1) {
Reporter.printFatalError(
"%%include_file command takes exactly 1 argument.");
}
llvm::StringRef IncludeFile = Args[0];
auto Buffer = llvm::MemoryBuffer::getFileAsStream(IncludeFile);
if (!Buffer)
Reporter.printFatalError(llvm::StringRef("Unable to open ") + IncludeFile);
llvm::StringRef Content = Buffer.get()->getBuffer();
// If the included file has %%begin() command listed, then we want to write
// only the content after the begin command.
// TODO: The way the content is split below does not allow space within the
// the parentheses and, before and after the command. This probably is too
// strict and should be relaxed.
auto P = Content.split("\n%%begin()\n");
if (P.second.empty()) {
// There was no %%begin in the content.
OS << P.first;
} else {
OS << P.second;
}
}
} // namespace llvm_libc

View File

@ -0,0 +1,32 @@
//===-------- Class which implements the %%include_file command -*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_UTILS_HDRGEN_INCLUDE_COMMAND_H
#define LLVM_LIBC_UTILS_HDRGEN_INCLUDE_COMMAND_H
#include "Command.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include <string>
namespace llvm_libc {
class IncludeFileCommand : public Command {
public:
static const char Name[];
void run(llvm::raw_ostream &OS, const ArgVector &Args,
llvm::StringRef StdHeader, llvm::RecordKeeper &Records,
const Command::ErrorReporter &Reporter) const override;
};
} // namespace llvm_libc
#endif // LLVM_LIBC_UTILS_HDRGEN_INCLUDE_COMMAND_H

View File

@ -0,0 +1,56 @@
//===---------------- "main" function of libc-hdrgen ------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "Generator.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/TableGen/Main.h"
#include <string>
#include <unordered_map>
namespace {
llvm::cl::opt<std::string>
HeaderDefFile("def", llvm::cl::desc("Path to the .h.def file."),
llvm::cl::value_desc("<filename>"), llvm::cl::Required);
llvm::cl::opt<std::string> StandardHeader(
"header",
llvm::cl::desc("The standard header file which is to be generated."),
llvm::cl::value_desc("<header file>"));
llvm::cl::list<std::string> ReplacementValues(
"args", llvm::cl::desc("Command seperated <argument name>=<value> pairs."),
llvm::cl::value_desc("<name=value>[,name=value]"));
void ParseArgValuePairs(std::unordered_map<std::string, std::string> &Map) {
for (std::string &R : ReplacementValues) {
auto Pair = llvm::StringRef(R).split('=');
Map[Pair.first] = Pair.second;
}
}
} // anonymous namespace
namespace llvm_libc {
bool HeaderGeneratorMain(llvm::raw_ostream &OS, llvm::RecordKeeper &Records) {
std::unordered_map<std::string, std::string> ArgMap;
ParseArgValuePairs(ArgMap);
Generator G(HeaderDefFile, StandardHeader, ArgMap);
G.generate(OS, Records);
return false;
}
} // namespace llvm_libc
int main(int argc, char *argv[]) {
llvm::cl::ParseCommandLineOptions(argc, argv);
return TableGenMain(argv[0], &llvm_libc::HeaderGeneratorMain);
}

View File

@ -0,0 +1,255 @@
//===--------------- Implementation of PublicAPICommand ----------*-C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "PublicAPICommand.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
static const char NamedTypeClassName[] = "NamedType";
static const char PtrTypeClassName[] = "PtrType";
static const char RestrictedPtrTypeClassName[] = "RestrictedPtrType";
static const char ConstTypeClassName[] = "ConstType";
static const char StructTypeClassName[] = "Struct";
static const char StandardSpecClassName[] = "StandardSpec";
static const char PublicAPIClassName[] = "PublicAPI";
static bool isa(llvm::Record *Def, llvm::Record *TypeClass) {
llvm::RecordRecTy *RecordType = Def->getType();
llvm::ArrayRef<llvm::Record *> Classes = RecordType->getClasses();
// We want exact types. That is, we don't want the classes listed in
// spec.td to be subclassed. Hence, we do not want the record |Def|
// to be of more than one class type..
if (Classes.size() != 1)
return false;
return Classes[0] == TypeClass;
}
// Text blocks for macro definitions and type decls can be indented to
// suit the surrounding tablegen listing. We need to dedent such blocks
// before writing them out.
static void dedentAndWrite(llvm::StringRef Text, llvm::raw_ostream &OS) {
llvm::SmallVector<llvm::StringRef, 10> Lines;
llvm::SplitString(Text, Lines, "\n");
size_t shortest_indent = 1024;
for (llvm::StringRef L : Lines) {
llvm::StringRef Indent = L.take_while([](char c) { return c == ' '; });
size_t IndentSize = Indent.size();
if (Indent.size() == L.size()) {
// Line is all spaces so no point noting the indent.
continue;
}
if (IndentSize < shortest_indent)
shortest_indent = IndentSize;
}
for (llvm::StringRef L : Lines) {
if (L.size() >= shortest_indent)
OS << L.drop_front(shortest_indent) << '\n';
}
}
class APIGenerator {
llvm::StringRef StdHeader;
// TableGen classes in spec.td.
llvm::Record *NamedTypeClass;
llvm::Record *PtrTypeClass;
llvm::Record *RestrictedPtrTypeClass;
llvm::Record *ConstTypeClass;
llvm::Record *StructClass;
llvm::Record *StandardSpecClass;
llvm::Record *PublicAPIClass;
using NameToRecordMapping = std::unordered_map<std::string, llvm::Record *>;
using NameSet = std::unordered_set<std::string>;
// Mapping from names to records defining them.
NameToRecordMapping MacroSpecMap;
NameToRecordMapping TypeSpecMap;
NameToRecordMapping FunctionSpecMap;
NameToRecordMapping MacroDefsMap;
NameToRecordMapping TypeDeclsMap;
NameSet Structs;
NameSet Functions;
bool isaNamedType(llvm::Record *Def) { return isa(Def, NamedTypeClass); }
bool isaStructType(llvm::Record *Def) { return isa(Def, StructClass); }
bool isaPtrType(llvm::Record *Def) { return isa(Def, PtrTypeClass); }
bool isaConstType(llvm::Record *Def) { return isa(Def, ConstTypeClass); }
bool isaRestrictedPtrType(llvm::Record *Def) {
return isa(Def, RestrictedPtrTypeClass);
}
bool isaStandardSpec(llvm::Record *Def) {
return isa(Def, StandardSpecClass);
}
bool isaPublicAPI(llvm::Record *Def) { return isa(Def, PublicAPIClass); }
std::string getTypeAsString(llvm::Record *TypeRecord) {
if (isaNamedType(TypeRecord) || isaStructType(TypeRecord)) {
return TypeRecord->getValueAsString("Name");
} else if (isaPtrType(TypeRecord)) {
return getTypeAsString(TypeRecord->getValueAsDef("PointeeType")) + " *";
} else if (isaConstType(TypeRecord)) {
return std::string("const ") +
getTypeAsString(TypeRecord->getValueAsDef("UnqualifiedType"));
} else if (isaRestrictedPtrType(TypeRecord)) {
return getTypeAsString(TypeRecord->getValueAsDef("PointeeType")) +
" *__restrict";
} else {
llvm::PrintFatalError(TypeRecord->getLoc(), "Invalid type.\n");
}
}
void indexStandardSpecDef(llvm::Record *StandardSpec) {
auto HeaderSpecList = StandardSpec->getValueAsListOfDefs("Headers");
for (llvm::Record *HeaderSpec : HeaderSpecList) {
if (HeaderSpec->getValueAsString("Name") == StdHeader) {
auto MacroSpecList = HeaderSpec->getValueAsListOfDefs("Macros");
// TODO: Trigger a fatal error on duplicate specs.
for (llvm::Record *MacroSpec : MacroSpecList)
MacroSpecMap[MacroSpec->getValueAsString("Name")] = MacroSpec;
auto TypeSpecList = HeaderSpec->getValueAsListOfDefs("Types");
for (llvm::Record *TypeSpec : TypeSpecList)
TypeSpecMap[TypeSpec->getValueAsString("Name")] = TypeSpec;
auto FunctionSpecList = HeaderSpec->getValueAsListOfDefs("Functions");
for (llvm::Record *FunctionSpec : FunctionSpecList) {
FunctionSpecMap[FunctionSpec->getValueAsString("Name")] =
FunctionSpec;
}
}
}
}
void indexPublicAPIDef(llvm::Record *PublicAPI) {
// While indexing the public API, we do not check if any of the entities
// requested is from an included standard. Such a check is done while
// generating the API.
auto MacroDefList = PublicAPI->getValueAsListOfDefs("Macros");
for (llvm::Record *MacroDef : MacroDefList)
MacroDefsMap[MacroDef->getValueAsString("Name")] = MacroDef;
auto TypeDeclList = PublicAPI->getValueAsListOfDefs("TypeDeclarations");
for (llvm::Record *TypeDecl : TypeDeclList)
TypeDeclsMap[TypeDecl->getValueAsString("Name")] = TypeDecl;
auto StructList = PublicAPI->getValueAsListOfStrings("Structs");
for (llvm::StringRef StructName : StructList)
Structs.insert(StructName);
auto FunctionList = PublicAPI->getValueAsListOfStrings("Functions");
for (llvm::StringRef FunctionName : FunctionList)
Functions.insert(FunctionName);
}
void index(llvm::RecordKeeper &Records) {
NamedTypeClass = Records.getClass(NamedTypeClassName);
PtrTypeClass = Records.getClass(PtrTypeClassName);
RestrictedPtrTypeClass = Records.getClass(RestrictedPtrTypeClassName);
StructClass = Records.getClass(StructTypeClassName);
ConstTypeClass = Records.getClass(ConstTypeClassName);
StandardSpecClass = Records.getClass(StandardSpecClassName);
PublicAPIClass = Records.getClass(PublicAPIClassName);
const auto &DefsMap = Records.getDefs();
for (auto &Pair : DefsMap) {
llvm::Record *Def = Pair.second.get();
if (isaStandardSpec(Def))
indexStandardSpecDef(Def);
if (isaPublicAPI(Def)) {
if (Def->getValueAsString("HeaderName") == StdHeader)
indexPublicAPIDef(Def);
}
}
}
public:
APIGenerator(llvm::StringRef Header, llvm::RecordKeeper &Records)
: StdHeader(Header) {
index(Records);
}
void write(llvm::raw_ostream &OS) {
for (auto &Pair : MacroDefsMap) {
const std::string &Name = Pair.first;
if (MacroSpecMap.find(Name) == MacroSpecMap.end())
llvm::PrintFatalError(Name + " not found in any standard spec.\n");
llvm::Record *MacroDef = Pair.second;
dedentAndWrite(MacroDef->getValueAsString("Defn"), OS);
OS << '\n';
}
for (auto &Pair : TypeDeclsMap) {
const std::string &Name = Pair.first;
if (TypeSpecMap.find(Name) == TypeSpecMap.end())
llvm::PrintFatalError(Name + " not found in any standard spec.\n");
llvm::Record *TypeDecl = Pair.second;
dedentAndWrite(TypeDecl->getValueAsString("Decl"), OS);
OS << '\n';
}
OS << "__BEGIN_C_DECLS\n\n";
for (auto &Name : Functions) {
if (FunctionSpecMap.find(Name) == FunctionSpecMap.end())
llvm::PrintFatalError(Name + " not found in any standard spec.\n");
llvm::Record *FunctionSpec = FunctionSpecMap[Name];
llvm::Record *RetValSpec = FunctionSpec->getValueAsDef("Return");
llvm::Record *ReturnType = RetValSpec->getValueAsDef("ReturnType");
OS << getTypeAsString(ReturnType) << " " << Name << "(";
auto ArgsList = FunctionSpec->getValueAsListOfDefs("Args");
for (size_t i = 0; i < ArgsList.size(); ++i) {
llvm::Record *ArgType = ArgsList[i]->getValueAsDef("ArgType");
OS << getTypeAsString(ArgType);
if (i < ArgsList.size() - 1)
OS << ", ";
}
OS << ");\n\n";
}
OS << "__END_C_DECLS\n";
}
};
namespace llvm_libc {
void writePublicAPI(llvm::raw_ostream &OS, llvm::RecordKeeper &Records) {}
const char PublicAPICommand::Name[] = "public_api";
void PublicAPICommand::run(llvm::raw_ostream &OS, const ArgVector &Args,
llvm::StringRef StdHeader,
llvm::RecordKeeper &Records,
const Command::ErrorReporter &Reporter) const {
if (Args.size() != 0) {
Reporter.printFatalError("public_api command does not take any arguments.");
}
APIGenerator G(StdHeader, Records);
G.write(OS);
}
} // namespace llvm_libc

View File

@ -0,0 +1,36 @@
//===---------- Implementation of PublicAPICommand --------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "Command.h"
#include "llvm/ADT/StringRef.h"
#include <string>
#include <unordered_map>
#include <unordered_set>
namespace llvm {
class raw_ostream;
class Record;
class RecordKeeper;
} // namespace llvm
namespace llvm_libc {
class PublicAPICommand : public Command {
public:
static const char Name[];
void run(llvm::raw_ostream &OS, const ArgVector &Args,
llvm::StringRef StdHeader, llvm::RecordKeeper &Records,
const Command::ErrorReporter &Reporter) const override;
};
} // namespace llvm_libc

View File

@ -1,188 +0,0 @@
#! /usr/bin/python
#===---------------- Script to generate header files ----------------------===#
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https:#llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
#===-----------------------------------------------------------------------===#
#
# This script takes a .h.def file and generates a .h header file.
# See docs/header_generation.md for more information.
#
#===-----------------------------------------------------------------------===#
import argparse
import contextlib
import os
import sys
COMMAND_PREFIX = "%%"
COMMENT_PREFIX = "<!>"
BEGIN_COMMAND = "begin"
COMMENT_COMMAND = "comment"
INCLUDE_FILE_COMMAND = "include_file"
class _Location(object):
def __init__(self, filename, line_number):
self.filename = filename
self.line_number = line_number
def __str__(self):
return "%s:%s" % (self.filename, self.line_number)
@contextlib.contextmanager
def output_stream_manager(filename):
if filename is None:
try:
yield sys.stdout
finally:
pass
else:
output_stream = open(filename, "w")
try:
yield output_stream
finally:
output_stream.close()
def _parse_command(loc, line):
open_paren = line.find("(")
if open_paren < 0 or line[-1] != ")":
return _fatal_error(loc, "Incorrect header generation command syntax.")
command_name = line[len(COMMAND_PREFIX):open_paren]
args = line[open_paren + 1:-1].split(",")
args = [a.strip() for a in args]
if len(args) == 1 and not args[0]:
# There are no args, so we will make the args list an empty list.
args = []
return command_name.strip(), args
def _is_named_arg(token):
if token.startswith("${") and token.endswith("}"):
return True
else:
return False
def _get_arg_name(token):
return token[2:-1]
def _fatal_error(loc, msg):
sys.exit("ERROR:%s: %s" % (loc, msg))
def _is_begin_command(line):
if line.startswith(COMMAND_PREFIX + BEGIN_COMMAND):
return True
def include_file_command(out_stream, loc, args, values):
if len(args) != 1:
_fatal_error(loc, "`%%include_file` command takes exactly one "
"argument. %d given." % len(args))
include_file_path = args[0]
if _is_named_arg(include_file_path):
arg_name = _get_arg_name(include_file_path)
include_file_path = values.get(arg_name)
if not include_file_path:
_fatal_error(
loc,
"No value specified for argument '%s'." % arg_name)
if not os.path.exists(include_file_path):
_fatal_error(
loc,
"Include file %s not found." % include_file_path)
with open(include_file_path, "r") as include_file:
begin = False
for line in include_file.readlines():
line = line.strip()
if _is_begin_command(line):
# Parse the command to make sure there are no errors.
command_name, args = _parse_command(loc, line)
if args:
_fatal_error(loc, "Begin command does not take any args.")
begin = True
# Skip the line on which %%begin() is listed.
continue
if begin:
out_stream.write(line + "\n")
def begin_command(out_stream, loc, args, values):
# "begin" command can only occur in a file included with %%include_file
# command. It is not a replacement command. Hence, we just fail with
# a fatal error.
_fatal_error(loc, "Begin command cannot be listed in an input file.")
# Mapping from a command name to its implementation function.
REPLACEMENT_COMMANDS = {
INCLUDE_FILE_COMMAND: include_file_command,
BEGIN_COMMAND: begin_command,
}
def apply_replacement_command(out_stream, loc, line, values):
if not line.startswith(COMMAND_PREFIX):
# This line is not a replacement command.
return line
command_name, args = _parse_command(loc, line)
command = REPLACEMENT_COMMANDS.get(command_name.strip())
if not command:
_fatal_error(loc, "Unknown replacement command `%`", command_name)
command(out_stream, loc, args, values)
def parse_options():
parser = argparse.ArgumentParser(
description="Script to generate header files from .def files.")
parser.add_argument("def_file", metavar="DEF_FILE",
help="Path to the .def file.")
parser.add_argument("--args", "-P", nargs= "*", default=[],
help="NAME=VALUE pairs for command arguments in the "
"input .def file.")
# The output file argument is optional. If not specified, the generated
# header file content will be written to stdout.
parser.add_argument("--out-file", "-o",
help="Path to the generated header file. Defaults to "
"stdout")
opts = parser.parse_args()
if not all(["=" in arg for arg in opts.args]):
# We want all args to be specified in the form "name=value".
_fatal_error(
__file__ + ":" + "[command line]",
"Command arguments should be listed in the form NAME=VALUE")
return opts
def main():
opts = parse_options()
arg_values = {}
for name_value_pair in opts.args:
name, value = name_value_pair.split("=")
arg_values[name] = value
with open(opts.def_file, "r") as def_file:
loc = _Location(opts.def_file, 0)
with output_stream_manager(opts.out_file) as out_stream:
for line in def_file:
loc.line_number += 1
line = line.strip()
if line.startswith(COMMAND_PREFIX):
replacement_text = apply_replacement_command(
out_stream, loc, line, arg_values)
out_stream.write("\n")
elif line.startswith(COMMENT_PREFIX):
# Ignore comment line
continue
else:
out_stream.write(line + "\n")
if __name__ == "__main__":
main()