76e64d96bb
Co-authored-by: Matija Kecman <matijak@google.com> Co-authored-by: Sebastian Noreña Rendón <sebas.norena@creativa77.com.ar> Co-authored-by: Kyle Bayes <kylebayes@google.com> PiperOrigin-RevId: 826094938 Change-Id: Id2658a187bc078056f4410c71202696753319e64
202 lines
6.6 KiB
Python
202 lines
6.6 KiB
Python
# Copyright 2025 DeepMind Technologies Limited
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Parser for MuJoCo structs."""
|
|
|
|
import collections
|
|
from typing import Dict, List
|
|
|
|
from introspect import ast_nodes
|
|
from introspect import structs
|
|
|
|
from wasm.codegen.helpers import common
|
|
from wasm.codegen.helpers import constants
|
|
from wasm.codegen.helpers import struct_constructor_code_builder
|
|
from wasm.codegen.helpers import struct_field_handler
|
|
from wasm.codegen.helpers import structs_wrappers_data
|
|
|
|
|
|
WrappedFieldData = structs_wrappers_data.WrappedFieldData
|
|
WrappedStructData = structs_wrappers_data.WrappedStructData
|
|
StructFieldHandler = struct_field_handler.StructFieldHandler
|
|
|
|
AnonymousStructDecl = ast_nodes.AnonymousStructDecl
|
|
StructFieldDecl = ast_nodes.StructFieldDecl
|
|
debug_print = common.debug_print
|
|
|
|
introspect_structs = structs.STRUCTS
|
|
|
|
|
|
def generate_wasm_bindings(
|
|
wrapped_structs: Dict[str, WrappedStructData],
|
|
) -> Dict[str, WrappedStructData]:
|
|
"""Generates WASM bindings for MuJoCo structs."""
|
|
|
|
for struct_name, wrap_data in wrapped_structs.items():
|
|
if struct_name in introspect_structs:
|
|
struct_fields = introspect_structs[struct_name].fields
|
|
elif struct_name in constants.ANONYMOUS_STRUCTS:
|
|
anonymous_struct = _get_anonymous_struct_field(struct_name)
|
|
if not anonymous_struct or not isinstance(
|
|
anonymous_struct.type, AnonymousStructDecl
|
|
):
|
|
raise RuntimeError(f"Anonymous struct not found: {struct_name}")
|
|
struct_fields = anonymous_struct.type.fields
|
|
else:
|
|
raise RuntimeError(f"Struct not found: {struct_name}")
|
|
|
|
debug_print(f"Wrapping struct: {struct_name}")
|
|
|
|
fields_with_init: List[WrappedFieldData] = []
|
|
for field in struct_fields:
|
|
field_gen = StructFieldHandler(field, wrap_data.wrap_name).generate()
|
|
# If the struct has at least one non-primitive or fixed size field
|
|
# we avoid shallow copy to avoid uninitialized memory.
|
|
if not field_gen.is_primitive_or_fixed_size:
|
|
wrap_data.use_shallow_copy = False
|
|
if field_gen.initialization:
|
|
fields_with_init.append(field_gen)
|
|
wrap_data.wrapped_fields.append(field_gen)
|
|
|
|
wrap_data.wrapped_header = (
|
|
struct_constructor_code_builder.build_struct_header(
|
|
struct_name,
|
|
wrap_data.use_shallow_copy,
|
|
fields_with_init,
|
|
wrap_data.wrapped_fields,
|
|
)
|
|
)
|
|
wrap_data.wrapped_source = (
|
|
struct_constructor_code_builder.build_struct_source(
|
|
struct_name,
|
|
get_default_func_name(struct_name),
|
|
fields_with_init,
|
|
wrap_data.use_shallow_copy,
|
|
)
|
|
)
|
|
return wrapped_structs
|
|
|
|
|
|
def _get_anonymous_struct_field(
|
|
anonymous_structs_key: str,
|
|
) -> StructFieldDecl | None:
|
|
"""Looks up the given key in the anonymous_structs dict and generates bindings for its fields."""
|
|
info = constants.ANONYMOUS_STRUCTS[anonymous_structs_key]
|
|
parent_decl = introspect_structs[info["parent"]]
|
|
target_field = next(
|
|
(
|
|
f
|
|
for f in parent_decl.fields
|
|
if hasattr(f, "name")
|
|
and f.name == info["field_name"]
|
|
and hasattr(f, "type")
|
|
and isinstance(f.type, AnonymousStructDecl)
|
|
),
|
|
None,
|
|
)
|
|
return target_field
|
|
|
|
|
|
def get_default_func_name(struct_name: str) -> str:
|
|
"""Returns the default function name for the given struct."""
|
|
if (
|
|
struct_name in constants.ANONYMOUS_STRUCTS.keys()
|
|
or struct_name in constants.NO_DEFAULT_CONSTRUCTORS
|
|
or (
|
|
common.uppercase_first_letter(struct_name)
|
|
in constants.MANUALLY_ADDED_FIELDS_FROM_TEMPLATE.keys()
|
|
)
|
|
):
|
|
return ""
|
|
elif struct_name.startswith("mjs"):
|
|
return f"mjs_default{struct_name.removeprefix('mjs')}"
|
|
elif struct_name.startswith("mjv"):
|
|
return f"mjv_default{struct_name.removeprefix('mjv')}"
|
|
else:
|
|
return f"mj_default{struct_name.removeprefix('mj')}"
|
|
|
|
|
|
def _get_field_struct_type(field_type):
|
|
"""Extracts the base struct name if the field type is a struct or pointer to a struct."""
|
|
if isinstance(field_type, ast_nodes.ValueType):
|
|
return field_type.name
|
|
if isinstance(field_type, ast_nodes.PointerType):
|
|
if isinstance(field_type.inner_type, ast_nodes.ValueType):
|
|
return field_type.inner_type.name
|
|
return None
|
|
|
|
|
|
def sort_structs_by_dependency(struct_names: List[str]) -> List[str]:
|
|
"""Sorts structs based on their field dependencies using topological sort.
|
|
|
|
Structs with no dependencies on other structs in the list come first.
|
|
If struct A has a field of type struct B, B must come before A in the
|
|
sorted list.
|
|
|
|
Args:
|
|
struct_names: A list of struct names to sort.
|
|
|
|
Returns:
|
|
A new list of struct names sorted by dependency.
|
|
|
|
Raises:
|
|
RuntimeError: If a cyclic dependency is detected.
|
|
"""
|
|
adj = collections.defaultdict(list)
|
|
in_degree = collections.defaultdict(int)
|
|
struct_set = set(struct_names)
|
|
sorted_struct_names = sorted(struct_names)
|
|
|
|
for struct_name in sorted_struct_names:
|
|
if struct_name not in introspect_structs:
|
|
# Skip anonymous or other structs not in the main introspect map
|
|
continue
|
|
|
|
struct_decl = introspect_structs[struct_name]
|
|
for field in struct_decl.fields:
|
|
if isinstance(field, ast_nodes.AnonymousStructDecl):
|
|
continue
|
|
|
|
field_type_name = _get_field_struct_type(field.type)
|
|
if (
|
|
field_type_name
|
|
and field_type_name != struct_name
|
|
and field_type_name in struct_set
|
|
):
|
|
if struct_name not in adj[field_type_name]:
|
|
adj[field_type_name].append(struct_name)
|
|
in_degree[struct_name] += 1
|
|
|
|
queue = collections.deque(
|
|
[name for name in sorted_struct_names if in_degree[name] == 0]
|
|
)
|
|
sorted_list = []
|
|
|
|
while queue:
|
|
u = queue.popleft()
|
|
sorted_list.append(u)
|
|
for v in adj[u]:
|
|
in_degree[v] -= 1
|
|
if in_degree[v] == 0:
|
|
queue.append(v)
|
|
|
|
if len(sorted_list) == len(struct_names):
|
|
return sorted_list
|
|
else:
|
|
remaining = set(struct_names) - set(sorted_list)
|
|
raise RuntimeError(
|
|
"Cycle detected in struct dependencies, involving: "
|
|
f"{', '.join(sorted(list(remaining)))}"
|
|
)
|