Files
workinf_Blender_Wasm/blender-5.2.0/source/blender/makesdna/intern/makesdna.cc
2026-08-12 04:47:48 -04:00

1155 lines
38 KiB
C++

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
* SPDX-FileCopyrightText: 2002-2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup DNA
*
* \brief Struct parser for generating SDNA.
*
* \section aboutmakesdnac About makesdna tool
*
* `makesdna` creates a `.c` file with a long string of numbers that
* encode the Blender file format. It is fast, because it is basically
* a binary dump. There are some details to mind when reconstructing
* the file (endianness and byte-alignment).
*
* This little program scans all structs that need to be serialized,
* and determined the names and types of all members. It calculates
* how much memory (on disk or in ram) is needed to store that struct,
* and the offsets for reaching a particular one.
*
* There is a facility to get verbose output from `sdna`. Search for
* \ref debugSDNA. This int can be set to 0 (no output) to some int.
* Higher numbers give more output.
*/
#define DNA_DEPRECATED_ALLOW
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "BLI_set.hh"
#include "BLI_string_ref.hh"
#include "BLI_sys_types.h" /* For `intptr_t` support. */
#include "BLI_system.h" /* For #BLI_system_backtrace stub. */
#include "BLI_utildefines.h"
#include "BLI_vector.hh"
#include "BLI_vector_set.hh"
#include "DNA_genfile.h"
#include "DNA_sdna_types.h"
#include "dna_parse.h"
#include "dna_utils.h"
namespace blender {
/* Include files that will be used to generate makesdna output.
* By default, they match the blender_includefiles, but could be overridden via a command line
* argument for the purposes of regression testing. */
static Span<const char *> includefiles = dna::default_dna_header_filenames();
/* -------------------------------------------------------------------- */
/** \name Debugging
* \{ */
/**
* Variable to control debug output of makesdna.
* debugSDNA:
* - 0 = no output, except errors
* - 1 = detail actions
* - 2 = full trace, tell which names and types were found
* - 4 = full trace, plus all gritty details
*/
extern int debugSDNA; /* Defined in `dna_parse.cc`. */
#define DEBUG_PRINTF(debug_level, ...) \
{ \
if (debugSDNA > debug_level) { \
printf(__VA_ARGS__); \
} \
} \
((void)0)
/* stub for BLI_abort() */
#ifndef NDEBUG
void BLI_system_backtrace(FILE *fp)
{
(void)fp;
}
#endif
/** \} */
/* -------------------------------------------------------------------- */
/** \name Type Table
*
* \{ */
/** SDNA builtin or struct type. */
struct TypeInfo {
const std::string name;
/* Native, 32 bit and 64 bit platform sizes. */
short size_native;
short size_32;
short size_64;
/* Alignment requirements for 32 and 64 bit platforms. */
short align_32;
short align_64;
/* Struct or built-in type? */
bool is_struct;
};
/** Table of unique SDNA types. */
struct TypeTable {
/* Using a vector set with guaranteed insertion order, so that resulting SDNA
* types are always in the same order. */
class GetIDFn {
public:
StringRef operator()(const TypeInfo &info) const
{
return info.name;
}
};
CustomIDVectorSet<TypeInfo, GetIDFn> types;
void add_builtin(const StringRef name, const short size, const int expected_type_index)
{
BLI_assert(this->types.size() == expected_type_index);
UNUSED_VARS_NDEBUG(expected_type_index);
this->types.add_new({.name = name,
.size_native = size,
.size_32 = size,
.size_64 = size,
.align_32 = size,
.align_64 = size,
.is_struct = false});
}
/** Add a struct type if it doesn't exist yet. Sizes will be computed later. */
void add_struct(StringRefNull name)
{
this->types.add({.name = name,
.size_native = 0,
.size_32 = 0,
.size_64 = 0,
.align_32 = 0,
.align_64 = 0,
.is_struct = true});
}
/** Look up the index of an existing type. */
int lookup_index(StringRefNull name) const
{
return this->types.index_of_as(name);
}
/** Look up an existing type by name. */
TypeInfo &lookup(StringRefNull name)
{
/* Const cast is okay because only TypeInfo::name is used for #VectorSet hash and equality. */
return const_cast<TypeInfo &>(this->types[this->lookup_index(name)]);
}
const TypeInfo &lookup(StringRefNull name) const
{
return this->types[this->lookup_index(name)];
}
};
static TypeTable build_type_table(const Span<dna::ParsedStruct> parsed_structs)
{
TypeTable table;
/* Insert built-in types.
*
* \warning Order of function calls here must be aligned with #eSDNA_Type.
* \warning uint is not allowed! use in structs an unsigned int.
* \warning sizes must match #DNA_elem_type_size().
*/
table.add_builtin("char", 1, SDNA_TYPE_CHAR);
table.add_builtin("uchar", 1, SDNA_TYPE_UCHAR);
table.add_builtin("short", 2, SDNA_TYPE_SHORT);
table.add_builtin("ushort", 2, SDNA_TYPE_USHORT);
table.add_builtin("int", 4, SDNA_TYPE_INT);
/* NOTE: long isn't supported, these are place-holders to maintain alignment with #eSDNA_Type. */
table.add_builtin("long", 4, 5 /* SDNA_TYPE_LONG */);
table.add_builtin("ulong", 4, 6 /* SDNA_TYPE_ULONG */);
table.add_builtin("float", 4, SDNA_TYPE_FLOAT);
table.add_builtin("double", 8, SDNA_TYPE_DOUBLE);
table.add_builtin("int64_t", 8, SDNA_TYPE_INT64);
table.add_builtin("uint64_t", 8, SDNA_TYPE_UINT64);
table.add_builtin("void", 0, SDNA_TYPE_VOID);
table.add_builtin("int8_t", 1, SDNA_TYPE_INT8);
/* Fake place-holder struct definition used to get an identifier for raw, untyped bytes buffers
* in blend-files.
*
* It will be written into the blend-file's SDNA, but it must never be used in the source code.
* Trying to declare `struct raw_data` in DNA headers will cause a build error.
*
* NOTE: While not critical, since all blend-files before introduction of this 'raw_data'
* type/struct have been using the `0` value for raw data #BHead.SDNAnr, it's best to reserve
* that first struct index to this raw data explicitly. */
table.add_struct("raw_data"); /* SDNA_TYPE_RAW_DATA */
BLI_STATIC_ASSERT(SDNA_RAW_DATA_STRUCT_INDEX == 0, "'raw data' SDNA struct index should be 0")
/* Insert struct and member types. */
for (const dna::ParsedStruct &parsed_struct : parsed_structs) {
table.add_struct(parsed_struct.type_name);
for (const dna::ParsedMember &parsed_member : parsed_struct.members) {
table.add_struct(parsed_member.type_name);
}
}
return table;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Type Sizes and Alignment
* \{ */
/** Native byte size contributed by a single member to the struct. */
static int member_size_native(const TypeInfo &member_type, const dna::ParsedMember &parsed_member)
{
const char *cp = parsed_member.member_name.c_str();
const int namelen = int(parsed_member.member_name.size());
const int array_num = (cp[namelen - 1] == ']') ? DNA_member_array_num(cp) : 1;
/* Pointer size. */
if (cp[0] == '*' || cp[1] == '*') {
return sizeof(void *) * array_num;
}
return member_type.size_native * array_num;
}
static bool check_member_alignment(const TypeInfo &struct_info,
const TypeInfo &member_type,
const int len,
const int member_align_override,
const StringRefNull name,
const StringRefNull detail)
{
bool result = true;
const char *struct_name = struct_info.name.c_str();
const short type_size = member_type.size_native;
if (member_align_override > 0 && (len % member_align_override)) {
fprintf(stderr,
"Align %d error (%s) in struct: %s %s (add %d padding bytes)\n",
member_align_override,
detail.c_str(),
struct_name,
name.c_str(),
member_align_override - (len % member_align_override));
result = false;
}
if (!member_type.is_struct && type_size > 4 && (len % 8)) {
fprintf(stderr,
"Align 8 error (%s) in struct: %s %s (add %d padding bytes)\n",
detail.c_str(),
struct_name,
name.c_str(),
len % 8);
result = false;
}
if (type_size > 3 && (len % 4)) {
fprintf(stderr,
"Align 4 error (%s) in struct: %s %s (add %d padding bytes)\n",
detail.c_str(),
struct_name,
name.c_str(),
len % 4);
result = false;
}
if (type_size == 2 && (len % 2)) {
fprintf(stderr,
"Align 2 error (%s) in struct: %s %s (add %d padding bytes)\n",
detail.c_str(),
struct_name,
name.c_str(),
len % 2);
result = false;
}
return result;
}
#ifdef __EMSCRIPTEN__
static int align_size_for_web_wasm(const int size, const int alignment)
{
if (alignment <= 4) {
return size;
}
return (size + alignment - 1) & ~(alignment - 1);
}
#endif
/** Determine how many bytes are needed for each struct. */
static int compute_type_size_and_alignment(TypeTable &table,
Span<dna::ParsedStruct> parsed_structs)
{
bool dna_error = false;
/* Multiple iterations to handle nested structs. */
int unknown = int(parsed_structs.size());
while (unknown) {
const int lastunknown = unknown;
unknown = 0;
for (const dna::ParsedStruct &parsed_struct : parsed_structs) {
TypeInfo &struct_info = table.lookup(parsed_struct.type_name);
/* When size is not known yet. */
if (struct_info.size_native == 0) {
int size_native = 0;
int size_32 = 0;
int size_64 = 0;
/* Sizes of the largest field in a struct. */
int max_align_32 = 0;
int max_align_64 = 0;
/* check all members in struct */
for (const dna::ParsedMember &parsed_member : parsed_struct.members) {
const TypeInfo &member_type = table.lookup(parsed_member.type_name);
const char *cp = parsed_member.member_name.c_str();
const int namelen = int(parsed_member.member_name.size());
/* is it a pointer or function pointer? */
if (cp[0] == '*' || cp[1] == '*') {
/* has the name an extra length? (array) */
const int array_num = (cp[namelen - 1] == ']') ? DNA_member_array_num(cp) : 1;
if (array_num == 0) {
fprintf(stderr,
"Zero array size found or could not parse %s: '%.*s'\n",
struct_info.name.c_str(),
namelen + 1,
cp);
dna_error = true;
}
/* 4-8 aligned/ */
if (sizeof(void *) == 4) {
if (size_native % 4) {
fprintf(stderr,
"Align pointer error in struct (size_native 4): %s %s\n",
struct_info.name.c_str(),
cp);
dna_error = true;
}
}
else {
if (size_native % 8) {
fprintf(stderr,
"Align pointer error in struct (size_native 8): %s %s\n",
struct_info.name.c_str(),
cp);
dna_error = true;
}
}
if (size_64 % 8) {
fprintf(stderr,
"Align pointer error in struct (size_64 8): %s %s\n",
struct_info.name.c_str(),
cp);
dna_error = true;
}
size_native += member_size_native(member_type, parsed_member);
size_32 += 4 * array_num;
size_64 += 8 * array_num;
max_align_32 = std::max(max_align_32, 4);
max_align_64 = std::max(max_align_64, 8);
}
else if (cp[0] == '[') {
/* parsing can cause names "var" and "[3]"
* to be found for "float var [3]" */
fprintf(stderr,
"Parse error in struct, invalid member name: %s %s\n",
struct_info.name.c_str(),
cp);
dna_error = true;
}
else if (member_type.size_native) {
/* has the name an extra length? (array) */
const int array_num = (cp[namelen - 1] == ']') ? DNA_member_array_num(cp) : 1;
if (array_num == 0) {
fprintf(stderr,
"Zero array size found or could not parse %s: '%.*s'\n",
struct_info.name.c_str(),
namelen + 1,
cp);
dna_error = true;
}
/* struct alignment */
if (member_type.is_struct) {
if (sizeof(void *) == 8 && (size_native % 8)) {
fprintf(stderr,
"Align struct error: %s::%s (starts at %d on the native platform; "
"%d %% %zu = %d bytes)\n",
struct_info.name.c_str(),
cp,
size_native,
size_native,
sizeof(void *),
size_native % 8);
dna_error = true;
}
}
/* Per-member C++ alignment override from the parser. */
const int member_align = parsed_member.alignment;
#ifdef __EMSCRIPTEN__
/* WebAssembly32 keeps 64-bit integer and double members 8-byte aligned, unlike the
* traditional 32-bit ABI used by Blender's DNA generator. Keep generated 32-bit DNA
* offsets identical to the actual Emscripten C++ layout. */
const int web_member_alignment = std::max(int(member_type.align_32), member_align);
size_native = align_size_for_web_wasm(size_native, web_member_alignment);
size_32 = align_size_for_web_wasm(size_32, web_member_alignment);
#endif
/* Check 2-4-8 aligned, plus any stricter C++ alignment. */
if (!check_member_alignment(
struct_info, member_type, size_32, member_align, cp, "32 bit"))
{
dna_error = true;
}
if (!check_member_alignment(
struct_info, member_type, size_64, member_align, cp, "64 bit"))
{
dna_error = true;
}
size_native += member_size_native(member_type, parsed_member);
size_32 += array_num * member_type.size_32;
size_64 += array_num * member_type.size_64;
max_align_32 = std::max<int>(max_align_32, member_type.align_32);
max_align_64 = std::max<int>(max_align_64, member_type.align_64);
max_align_32 = std::max<int>(max_align_32, member_align);
max_align_64 = std::max<int>(max_align_64, member_align);
}
else {
size_native = 0;
size_32 = 0;
size_64 = 0;
break;
}
}
if (size_native == 0) {
unknown++;
}
else {
/* Sanity check: struct sizes are written as #short to the SDNA blob. */
BLI_assert(size_native <= SHRT_MAX);
BLI_assert(size_32 <= SHRT_MAX);
BLI_assert(size_64 <= SHRT_MAX);
/* Sanity check 1: alignment should never be 0. */
BLI_assert(max_align_32);
BLI_assert(max_align_64);
/* Sanity check 2: alignment should always be equal or smaller than the maximum
* alignment we support. 8 bytes for built-in types (e.g. `int64_t`, `double`),
* up to 16 bytes for C++ over-aligned types like `float4x4`. */
BLI_assert(max_align_32 <= 16);
BLI_assert(max_align_64 <= 16);
#ifdef __EMSCRIPTEN__
size_native = align_size_for_web_wasm(size_native, max_align_32);
size_32 = align_size_for_web_wasm(size_32, max_align_32);
#endif
/* Store the finalized layout. On WebAssembly this must happen after the
* ABI-specific tail padding has been applied, otherwise parent structs
* use stale sizes for embedded 64-bit-aligned members. */
struct_info.size_native = short(size_native);
struct_info.size_32 = short(size_32);
struct_info.size_64 = short(size_64);
struct_info.align_32 = short(max_align_32);
struct_info.align_64 = short(max_align_64);
if (size_32 % max_align_32) {
/* There is an one odd case where only the 32 bit struct has alignment issues
* and the 64 bit does not, that can only be fixed by adding a padding pointer
* to the struct to resolve the problem. */
if ((size_64 % max_align_64 == 0) && (size_32 % max_align_32 == 4)) {
fprintf(stderr,
"Sizeerror in 32 bit struct: %s (add padding pointer)\n",
struct_info.name.c_str());
}
else {
fprintf(stderr,
"Sizeerror in 32 bit struct: %s (add %d bytes)\n",
struct_info.name.c_str(),
max_align_32 - (size_32 % max_align_32));
}
dna_error = true;
}
if (size_64 % max_align_64) {
fprintf(stderr,
"Sizeerror in 64 bit struct: %s (add %d bytes)\n",
struct_info.name.c_str(),
max_align_64 - (size_64 % max_align_64));
dna_error = true;
}
if (size_native % 4 && !ELEM(size_native, 1, 2)) {
fprintf(stderr,
"Sizeerror 4 in struct: %s (add %d bytes)\n",
struct_info.name.c_str(),
size_native % 4);
dna_error = true;
}
}
}
}
if (unknown == lastunknown) {
break;
}
}
if (unknown) {
fprintf(stderr, "ERROR: still %d structs unknown\n", unknown);
if (debugSDNA) {
fprintf(stderr, "*** Known structs :\n");
for (const dna::ParsedStruct &ps : parsed_structs) {
const TypeInfo &info = table.lookup(ps.type_name);
if (info.size_native != 0) {
fprintf(stderr, " %s\n", info.name.c_str());
}
}
}
fprintf(stderr, "*** Unknown structs :\n");
for (const dna::ParsedStruct &ps : parsed_structs) {
const TypeInfo &info = table.lookup(ps.type_name);
if (info.size_native == 0) {
fprintf(stderr, " %s\n", info.name.c_str());
}
}
dna_error = true;
}
return dna_error;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name DNA File Writing
* \{ */
/** Construct the DNA.c file */
static void dna_write(FILE *file, const void *pntr, const int size)
{
constexpr int MAX_DNA_LINE_LENGTH = 20;
static int linelength = 0;
const char *data = static_cast<const char *>(pntr);
for (int i = 0; i < size; i++) {
fprintf(file, "%d, ", data[i]);
linelength++;
if (linelength >= MAX_DNA_LINE_LENGTH) {
fprintf(file, "\n");
linelength = 0;
}
}
}
/** Serialize the SDNA tables to a file. */
static void write_sdna_blob(FILE *file,
const TypeTable &table,
const Span<dna::ParsedStruct> parsed_structs)
{
/* `raw_data` plus parsed structs. */
const int num_structs = 1 + int(parsed_structs.size());
/* Deduplicated member names in source order. */
VectorSet<StringRefNull> member_names;
for (const dna::ParsedStruct &ps : parsed_structs) {
for (const dna::ParsedMember &pm : ps.members) {
member_names.add(pm.member_name);
}
}
/* FOR DEBUG. */
if (debugSDNA > 1) {
printf("names_len %d types_len %d structs_len %d\n",
int(member_names.size()),
int(table.types.size()),
num_structs);
for (const StringRefNull name : member_names) {
printf(" %s\n", name.c_str());
}
printf("\n");
for (const TypeInfo &type : table.types) {
printf(" %s %d\n", type.name.c_str(), type.size_native);
}
printf("\n");
for (const dna::ParsedStruct &ps : parsed_structs) {
const TypeInfo &info = table.lookup(ps.type_name);
printf(" struct %s elems: %d size: %d\n",
info.name.c_str(),
int(ps.members.size()),
info.size_native);
for (const dna::ParsedMember &pm : ps.members) {
const TypeInfo &member_type = table.lookup(pm.type_name);
printf(" %s %s allign32:%d, allign64:%d\n",
member_type.name.c_str(),
pm.member_name.c_str(),
member_type.align_32,
member_type.align_64);
}
}
}
if (member_names.is_empty() || parsed_structs.is_empty()) {
return;
}
const char nil_bytes[4] = {0};
dna_write(file, "SDNA", 4);
/* Write NAME: member names. */
dna_write(file, "NAME", 4);
int len = int(member_names.size());
dna_write(file, &len, 4);
len = 0;
for (const StringRefNull member_name : member_names) {
const int member_len = int(member_name.size()) + 1;
dna_write(file, member_name.c_str(), member_len);
len += member_len;
}
int len_align = (len + 3) & ~3;
if (len != len_align) {
dna_write(file, nil_bytes, len_align - len);
}
/* Write TYPES: built-in and struct table. */
dna_write(file, "TYPE", 4);
len = int(table.types.size());
dna_write(file, &len, 4);
len = 0;
for (const TypeInfo &type : table.types) {
const int type_len = int(type.name.size()) + 1;
dna_write(file, type.name.c_str(), type_len);
len += type_len;
}
len_align = (len + 3) & ~3;
if (len != len_align) {
dna_write(file, nil_bytes, len_align - len);
}
/* WRITE TLEN: size of each type. */
dna_write(file, "TLEN", 4);
for (const TypeInfo &type : table.types) {
dna_write(file, &type.size_native, 2);
}
/* Pad to multiple of 4 bytes when types count is odd. */
if (table.types.size() & 1) {
dna_write(file, nil_bytes, 2);
}
/* WRITE STRUCTS */
dna_write(file, "STRC", 4);
dna_write(file, &num_structs, 4);
BLI_assert_msg(table.types.size() < SHRT_MAX, "SDNA only supports up to SHRT_MAX types");
/* Synthetic `raw_data` struct: type index, zero members. */
const short raw_data_header[2] = {short(table.lookup_index("raw_data")), 0};
dna_write(file, raw_data_header, 4);
for (const dna::ParsedStruct &ps : parsed_structs) {
const short header[2] = {short(table.lookup_index(ps.type_name)), short(ps.members.size())};
dna_write(file, header, 4);
for (const dna::ParsedMember &pm : ps.members) {
const short pair[2] = {short(table.lookup_index(pm.type_name)),
short(member_names.index_of_as(pm.member_name))};
dna_write(file, pair, 4);
}
}
/* No padding needed as each entry is 4 bytes already. */
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Auxiliary files
* \{ */
/** Write the `dna_type_offsets.h` for `SDNA_TYPE_FROM_STRUCT(id)` macro. */
static void write_sdna_type_offsets(FILE *file, const Span<dna::ParsedStruct> parsed_structs)
{
fprintf(file, "#pragma once\n");
fprintf(file, "#define SDNA_TYPE_FROM_STRUCT(id) _SDNA_TYPE_##id\n");
fprintf(file, "enum {\n");
fprintf(file, "\t_SDNA_TYPE_raw_data = %d,\n", SDNA_RAW_DATA_STRUCT_INDEX);
for (const int64_t i : parsed_structs.index_range()) {
const int sdna_index = int(i) + 1;
fprintf(
file, "\t_SDNA_TYPE_%s = %d,\n", parsed_structs[i].alias_type_name.c_str(), sdna_index);
}
fprintf(file, "\tSDNA_TYPE_MAX = %d,\n", 1 + int(parsed_structs.size()));
fprintf(file, "};\n\n");
}
/** Write the `dna_struct_ids.cc` file for `sdna_struct_id_get<T>()`. */
static void write_sdna_struct_ids(FILE *file, const Span<dna::ParsedStruct> parsed_structs)
{
fprintf(file, "#include \"DNA_sdna_type_ids.hh\"\n\n");
fprintf(file, "namespace blender {\n");
fprintf(file, "namespace dna {\n\n");
fprintf(file, "int sdna_struct_id_get_max() { return %d; }\n", int(parsed_structs.size()));
fprintf(file, "\n}\n");
for (const int64_t i : parsed_structs.index_range()) {
const char *name = parsed_structs[i].alias_type_name.c_str();
const int sdna_index = int(i) + 1;
fprintf(file, "struct %s;\n", name);
fprintf(
file, "template<> int dna::sdna_struct_id_get<%s>() { return %d; }\n", name, sdna_index);
}
fprintf(file, "\n}\n");
}
/** Write the `dna_defaults.cc` for RNA to automatically set property defaults. */
static void write_rna_defaults(FILE *file,
const StringRefNull base_directory,
const Span<dna::ParsedStruct> parsed_structs)
{
fprintf(file, "/* Default struct member values for RNA. */\n");
fprintf(file, "#define DNA_DEPRECATED_ALLOW\n");
fprintf(file, "#define DNA_NO_EXTERNAL_CONSTRUCTORS\n");
fprintf(file, "#include \"DNA_sdna_type_ids.hh\"\n\n");
for (const char *filename : includefiles) {
fprintf(file, "#include \"%s%s\"\n", base_directory.c_str(), filename);
}
fprintf(file, "namespace blender {\n\n");
/* Define an instance of each struct. */
for (const dna::ParsedStruct &parsed_struct : parsed_structs) {
const StringRefNull name = parsed_struct.type_name.c_str();
std::string default_var;
if (name == "bTheme") {
/* Exception for bTheme which is auto-generated. */
fprintf(file, "extern \"C\" const bTheme U_theme_default;\n");
default_var = "U_theme_default";
}
else {
fprintf(file, "static const %s DNA_DEFAULT_%s = {};\n", name.c_str(), name.c_str());
default_var = "DNA_DEFAULT_" + name;
}
/* Table with pointer to each member. */
fprintf(file, "static const void *const member_defaults_%s[] = {\n", name.c_str());
for (const dna::ParsedMember &pm : parsed_struct.members) {
const StringRef bare_id = DNA_member_id_string_ref(pm.member_name);
fprintf(file, " &%s.%.*s,\n", default_var.c_str(), int(bare_id.size()), bare_id.data());
}
fprintf(file, "};\n");
}
/* Table with all structs. */
fprintf(file, "\nextern const void *const *const DNA_member_default_table[] = {\n");
for (const dna::ParsedStruct &parsed_struct : parsed_structs) {
fprintf(file, " member_defaults_%s,\n", parsed_struct.type_name.c_str());
}
fprintf(file, "};\n\n");
fprintf(file, "} // namespace blender\n");
}
/** Write `dna_verify.cc` file to verify `sizeof` and `offsetof` match what we computed. */
static void write_sdna_verify(FILE *file,
const TypeTable &table,
const Span<dna::ParsedStruct> parsed_structs,
const StringRefNull base_directory)
{
fprintf(file, "/* Verify struct sizes and member offsets are as expected by DNA. */\n");
fprintf(file, "#include \"BLI_assert.h\"\n\n");
/* Needed so we can find offsets of deprecated structs. */
fprintf(file, "#define DNA_DEPRECATED_ALLOW\n");
/* Workaround enum naming collision in static asserts
* (ideally this included a unique name/id per file). */
fprintf(file, "#define assert_line_ assert_line_DNA_\n");
for (const char *filename : includefiles) {
fprintf(file, "#include \"%s%s\"\n", base_directory.c_str(), filename);
}
fprintf(file, "#undef assert_line_\n");
fprintf(file, "\n");
fprintf(file, "using namespace blender;\n");
fprintf(file, "\n");
for (const dna::ParsedStruct &parsed_struct : parsed_structs) {
int offset = 0;
for (const dna::ParsedMember &parsed_member : parsed_struct.members) {
const TypeInfo &member_type = table.lookup(parsed_member.type_name);
#ifdef __EMSCRIPTEN__
const char *member_name = parsed_member.member_name.c_str();
const int member_alignment =
(member_name[0] == '*' || member_name[1] == '*') ?
int(sizeof(void *)) :
std::max(int(member_type.align_32), parsed_member.alignment);
offset = align_size_for_web_wasm(offset, member_alignment);
#endif
const StringRef alias_id = DNA_member_id_string_ref(parsed_member.alias_member_name);
fprintf(file,
"BLI_STATIC_ASSERT(offsetof(struct %s, %.*s) == %d, \"DNA member offset "
"verify\");\n",
parsed_struct.alias_type_name.c_str(),
int(alias_id.size()),
alias_id.data(),
offset);
offset += member_size_native(member_type, parsed_member);
}
const TypeInfo &struct_info = table.lookup(parsed_struct.type_name);
fprintf(file,
"BLI_STATIC_ASSERT(sizeof(struct %s) == %d, \"DNA struct size verify\");\n\n",
parsed_struct.alias_type_name.c_str(),
struct_info.size_native);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Renaming
*
* Apply struct/member renames from `dna_rename_defs.h`.
* \{ */
static bool apply_renames(Vector<dna::ParsedStruct> &parsed_structs)
{
const DnaRenameMaps rename_maps = DNA_rename_maps_alias_to_static();
for (dna::ParsedStruct &parsed_struct : parsed_structs) {
/* Save the original C++ name and rewrite. */
parsed_struct.alias_type_name = parsed_struct.type_name;
parsed_struct.type_name = rename_maps.types.lookup_default_as(parsed_struct.type_name,
parsed_struct.type_name);
Set<StringRef> members_unique;
members_unique.reserve(parsed_struct.members.size());
for (dna::ParsedMember &parsed_member : parsed_struct.members) {
/* Save the original C++ type and rewrite. */
parsed_member.alias_member_name = parsed_member.member_name;
parsed_member.type_name = rename_maps.types.lookup_default_as(parsed_member.type_name,
parsed_member.type_name);
/* Rewrite the member name. */
std::string &member_name = parsed_member.member_name;
StringRef stripped = DNA_member_id_string_ref(member_name);
const StringRefNull *member_static = rename_maps.members.lookup_ptr(
{parsed_struct.type_name, std::string(stripped)});
if (member_static != nullptr) {
const size_t prefix_len = stripped.data() - member_name.data();
member_name.replace(prefix_len, stripped.size(), member_static->c_str());
stripped = DNA_member_id_string_ref(member_name);
}
/* Sanity check to ensure all member names are still unique. */
if (!members_unique.add(stripped)) {
fprintf(stderr,
"Error: duplicate name found '%s.%.*s', "
"likely cause is 'dna_rename_defs.h'\n",
parsed_struct.alias_type_name.c_str(),
int(stripped.size()),
stripped.data());
return false;
}
}
}
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Make DNA
*
* Parse header files to generate SDNA blob and auxiliary files.
* \{ */
static bool make_structDNA(const StringRefNull base_directory,
FILE *file,
FILE *file_offsets,
FILE *file_verify,
FILE *file_ids,
FILE *file_defaults)
{
if (debugSDNA > 0) {
fflush(stdout);
printf("Running makesdna at debug level %d\n", debugSDNA);
}
/* Parse structs and enums from all DNA header files. */
Vector<dna::ParsedStruct> parsed_structs;
Vector<dna::ParsedEnum> parsed_enums;
DEBUG_PRINTF(0, "\tStart of header scan:\n");
if (!dna::parse_dna_headers(base_directory, parsed_structs, parsed_enums, includefiles)) {
return false;
}
DEBUG_PRINTF(0, "\tFinished scanning headers.\n");
/* Write default values for RNA before any substitution or renaming, as RNA binds
* to the actual C++ data structures rather than SDNA. */
write_rna_defaults(file_defaults, base_directory, parsed_structs);
/* Substitute C++ types with C types known to SDNA. */
if (!dna::substitute_cpp_types(parsed_structs, parsed_enums, false)) {
return false;
}
/* Apply renames from `dna_rename_defs.h`. */
if (!apply_renames(parsed_structs)) {
return false;
}
/* Build type table. */
TypeTable table = build_type_table(parsed_structs);
/* Compute type sizes and check alignment. */
if (compute_type_size_and_alignment(table, parsed_structs)) {
return false;
}
/* Write SDNA blob. */
DEBUG_PRINTF(0, "Writing file ... ");
write_sdna_blob(file, table, parsed_structs);
/* Write auxiliary files. */
write_sdna_type_offsets(file_offsets, parsed_structs);
write_sdna_struct_ids(file_ids, parsed_structs);
write_sdna_verify(file_verify, table, parsed_structs, base_directory);
DEBUG_PRINTF(0, "done.\n");
return true;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Main Function
* \{ */
static void make_bad_file(const StringRefNull filename, int line)
{
FILE *fp = fopen(filename.c_str(), "w");
fprintf(fp,
"#error \"Error! can't make correct DNA.c file from %s:%d, check alignment.\"\n",
__FILE__,
line);
fclose(fp);
}
#ifndef BASE_HEADER
# define BASE_HEADER "../"
#endif
static void print_usage(const char *argv0)
{
printf(
"Usage: %s [--include-file <file>, ...] "
"dna.cc dna_type_offsets.h dna_verify.cc dna_struct_ids.cc dna_defaults.cc "
"[base directory]\n",
argv0);
}
} // namespace blender
int main(int argc, char **argv)
{
using namespace blender;
Vector<const char *> cli_include_files;
/* There is a number of non-optional arguments that must be provided to the executable. */
if (argc < 6) {
print_usage(argv[0]);
return 1;
}
/* Parse optional arguments. */
int arg_index = 1; /* Skip the argv0. */
while (arg_index < argc) {
if (STREQ(argv[arg_index], "--include-file")) {
++arg_index;
if (arg_index == argc) {
printf("Missing argument for --include-file\n");
print_usage(argv[0]);
return 1;
}
cli_include_files.append(argv[arg_index]);
++arg_index;
continue;
}
break;
}
if (!cli_include_files.is_empty()) {
includefiles = Span<const char *>(cli_include_files.data(), cli_include_files.size());
}
/* Check the number of non-optional positional arguments. */
const int num_arguments = argc - arg_index;
if (!ELEM(num_arguments, 5, 6)) {
print_usage(argv[0]);
return 0;
}
int return_status = 0;
FILE *file_dna = fopen(argv[arg_index], "w");
FILE *file_dna_offsets = fopen(argv[arg_index + 1], "w");
FILE *file_dna_verify = fopen(argv[arg_index + 2], "w");
FILE *file_dna_ids = fopen(argv[arg_index + 3], "w");
FILE *file_dna_defaults = fopen(argv[arg_index + 4], "w");
if (!file_dna) {
printf("Unable to open file: %s\n", argv[arg_index]);
return_status = 1;
}
else if (!file_dna_offsets) {
printf("Unable to open file: %s\n", argv[arg_index + 1]);
return_status = 1;
}
else if (!file_dna_verify) {
printf("Unable to open file: %s\n", argv[arg_index + 2]);
return_status = 1;
}
else if (!file_dna_ids) {
printf("Unable to open file: %s\n", argv[arg_index + 3]);
return_status = 1;
}
else if (!file_dna_defaults) {
printf("Unable to open file: %s\n", argv[arg_index + 4]);
return_status = 1;
}
else {
const char *base_directory;
if (num_arguments == 6) {
base_directory = argv[arg_index + 5];
}
else {
base_directory = BASE_HEADER;
}
/* NOTE: #init_structDNA() in dna_genfile.cc expects `sdna->data` is 4-bytes aligned.
* `DNAstr[]` buffer written by `makesdna` is used for this data, so make `DNAstr` forcefully
* 4-bytes aligned. */
#ifdef __GNUC__
# define FORCE_ALIGN_4 " __attribute__((aligned(4))) "
#else
# define FORCE_ALIGN_4 " "
#endif
fprintf(file_dna, "extern const unsigned char DNAstr[];\n");
fprintf(file_dna, "const unsigned char" FORCE_ALIGN_4 "DNAstr[] = {\n");
#undef FORCE_ALIGN_4
if (!make_structDNA(base_directory,
file_dna,
file_dna_offsets,
file_dna_verify,
file_dna_ids,
file_dna_defaults))
{
/* error */
fclose(file_dna);
file_dna = nullptr;
make_bad_file(argv[1], __LINE__);
return_status = 1;
}
else {
fprintf(file_dna, "};\n");
fprintf(file_dna, "extern const int DNAlen;\n");
fprintf(file_dna, "const int DNAlen = sizeof(DNAstr);\n");
}
}
if (file_dna) {
fclose(file_dna);
}
if (file_dna_offsets) {
fclose(file_dna_offsets);
}
if (file_dna_verify) {
fclose(file_dna_verify);
}
if (file_dna_ids) {
fclose(file_dna_ids);
}
if (file_dna_defaults) {
fclose(file_dna_defaults);
}
return return_status;
}
/* handy but fails on struct bounds which makesdna doesn't care about
* with quite the same strictness as GCC does */
#if 0
/* include files for automatic dependencies */
/* extra safety check that we are aligned,
* warnings here are easier to fix the makesdna's */
# ifdef __GNUC__
# pragma GCC diagnostic error "-Wpadded"
# endif
#endif /* if 0 */
/* The include file below is automatically generated from the `SRC_DNA_INC`
* variable in 'source/blender/CMakeLists.txt'. */
#include "dna_includes_all.h"
/* end of list */
/** \} */
/* -------------------------------------------------------------------- */
/** \name DNA Renaming Sanity Check
*
* Without this it's possible to reference struct members that don't exist,
* breaking backward & forward compatibility.
*
* \{ */
static void UNUSED_FUNCTION(dna_rename_defs_ensure)()
{
using namespace blender;
#define DNA_STRUCT_RENAME(old, new) (void)sizeof(new);
#define DNA_STRUCT_RENAME_MEMBER(new_struct_name, old, new) (void)offsetof(new_struct_name, new);
#include "dna_rename_defs.h"
#undef DNA_STRUCT_RENAME
#undef DNA_STRUCT_RENAME_MEMBER
}
/** \} */