diff --git a/MODULE.bazel b/MODULE.bazel index f8c68525..4164c4e9 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,3 +1,17 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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. + module( name = "emboss", version = "0.0.0", @@ -18,6 +32,16 @@ bazel_dep(name = "rules_python", version = "1.0.0") bazel_dep(name = "rules_cc", version = "0.1.0") bazel_dep(name = "rules_shell", version = "0.3.0") bazel_dep(name = "rules_license", version = "0.0.8") +bazel_dep(name = "rules_rust", version = "0.71.3") + +rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") +rust.toolchain( + edition = "2021", + versions = ["1.79.0"], +) +use_repo(rust, "rust_toolchains") + +register_toolchains("@rust_toolchains//:all") pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip") pip.parse( diff --git a/build_defs.bzl b/build_defs.bzl index f2d21e89..12a34e8c 100644 --- a/build_defs.bzl +++ b/build_defs.bzl @@ -27,6 +27,7 @@ There is also a convenience macro, `emboss_cc_library()`, which creates an load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain", "use_cc_toolchain") load("@rules_cc//cc/common:cc_common.bzl", "cc_common") load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("@rules_rust//rust:defs.bzl", "rust_library") def emboss_cc_library(name, srcs, deps = [], import_dirs = [], enable_enum_traits = True, **kwargs): """Constructs a C++ library from an .emb file. @@ -273,3 +274,105 @@ cc_emboss_library = rule( }, provides = [CcInfo, EmbossInfo], ) + +def _rust_emboss_codegen_impl(ctx): + if len(ctx.attr.deps) != 1: + fail("`deps` attribute must contain exactly one label.", attr = "deps") + dep = ctx.attr.deps[0] + emboss_info = dep[EmbossInfo] + + src = emboss_info.direct_source + out_file = ctx.actions.declare_file(src.basename + ".rs") + + args = ctx.actions.args() + args.add("--input-file") + args.add_all(emboss_info.direct_ir) + args.add("--output-file") + args.add(out_file) + + ctx.actions.run( + inputs = emboss_info.direct_ir, + outputs = [out_file], + executable = ctx.executable._emboss_rust_compiler, + arguments = [args], + mnemonic = "EmbossRust", + ) + + return [DefaultInfo(files = depset([out_file]))] + +_rust_emboss_codegen = rule( + implementation = _rust_emboss_codegen_impl, + attrs = { + "deps": attr.label_list( + providers = [EmbossInfo], + allow_rules = ["emboss_library"], + allow_files = False, + ), + "_emboss_rust_compiler": attr.label( + executable = True, + cfg = "exec", + default = Label("//compiler/back_end/experimental/rust:emboss_codegen_rust"), + ), + }, +) + +def _derive_workspace_path(src, package): + if src.startswith("//"): + if ":" in src: + pkg, name = src[2:].split(":", 1) + else: + parts = src[2:].split("/") + pkg = "/".join(parts[:-1]) + name = parts[-1] + return pkg + "/" + name if pkg else name + elif src.startswith("@"): + return src.replace(":", "/").replace("@", "") + else: + return package + "/" + src if package else src + +def _derive_crate_name(src, package): + path = _derive_workspace_path(src, package) + return path.replace("/", "_").replace(".", "_").replace("-", "_") + +def emboss_rust_library(name, srcs, deps = [], rust_deps = [], import_dirs = [], **kwargs): + """Constructs a Rust library from an .emb file. + + Args: + name: The name of the resulting rust_library. + srcs: A list containing exactly one .emb source file. + deps: A list of emboss_rust_library dependencies. + rust_deps: Dependencies to forward to the rust_library. + import_dirs: A list of directories to add to the import path. + **kwargs: Additional arguments to pass to rust_library. + """ + if len(srcs) != 1: + fail( + "Must specify exactly one Emboss source file for emboss_rust_library.", + "srcs", + ) + + emboss_library_name = name + "_ir" + + emboss_library( + name = emboss_library_name, + srcs = srcs, + deps = [dep + "_ir" for dep in deps], + import_dirs = import_dirs, + ) + + codegen_name = name + "_srcs" + + _rust_emboss_codegen( + name = codegen_name, + deps = [":" + emboss_library_name], + ) + + crate_name = kwargs.pop("crate_name", _derive_crate_name(srcs[0], native.package_name())) + + rust_library( + name = name, + srcs = [":" + codegen_name], + crate_name = crate_name, + deps = rust_deps + deps + ["//runtime/experimental/rust:emboss_runtime"], + **kwargs + ) diff --git a/compiler/back_end/experimental/__init__.py b/compiler/back_end/experimental/__init__.py new file mode 100644 index 00000000..75edc9bb --- /dev/null +++ b/compiler/back_end/experimental/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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. diff --git a/compiler/back_end/experimental/rust/BUILD b/compiler/back_end/experimental/rust/BUILD new file mode 100644 index 00000000..fa561432 --- /dev/null +++ b/compiler/back_end/experimental/rust/BUILD @@ -0,0 +1,37 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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. + +load("@rules_python//python:py_binary.bzl", "py_binary") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = [ + "//visibility:private", + ], +) + +py_binary( + name = "emboss_codegen_rust", + srcs = ["emboss_codegen_rust.py"], + data = ["generated_code_templates"], + visibility = ["//visibility:public"], + deps = [ + "//compiler/back_end/util:code_template", + "//compiler/front_end:module_ir", + "//compiler/util:error", + "//compiler/util:ir_data", + "//compiler/util:ir_util", + "//compiler/util:resources", + ], +) diff --git a/compiler/back_end/experimental/rust/__init__.py b/compiler/back_end/experimental/rust/__init__.py new file mode 100644 index 00000000..1de4256e --- /dev/null +++ b/compiler/back_end/experimental/rust/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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. + +# Init file for rust backend. diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py new file mode 100644 index 00000000..89a4ec71 --- /dev/null +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -0,0 +1,963 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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. + +"""Emboss Rust code generator.""" + +import os +import sys +from typing import Literal + +from compiler.back_end.util import code_template +from compiler.util import error +from compiler.util import ir_data +from compiler.util import ir_data_utils +from compiler.util import ir_util +from compiler.util import resources +import argparse + +ColorOutput = Literal["always", "never", "if_tty", "auto"] +Source = str +Diagnostics = list[list[error._Message]] +ErrorList = list[list[error._Message]] + +_TEMPLATE_FILE_NAME = "generated_code_templates" +_UNSUPPORTED_PRELUDE_TYPES = {"Bcd", "Float"} +_SYNTHETIC_ATTRIBUTES = {"expected_back_ends", "fixed_size_in_bits"} + + +def _show_errors( + errors: ErrorList, ir: ir_data.EmbossIr, color_output: ColorOutput +) -> None: + """Prints errors with source code snippets.""" + source_codes = {} + if ir: + for module in ir.module: + source_codes[module.source_file_name] = module.source_text + use_color = color_output == "always" or ( + color_output in ("auto", "if_tty") and os.isatty(sys.stderr.fileno()) + ) + print(error.format_errors(errors, source_codes, use_color), file=sys.stderr) + + +def _load_templates(): + return code_template.parse_templates( + resources.load("compiler.back_end.experimental.rust", _TEMPLATE_FILE_NAME) + ) + + +def generate_code(ir: ir_data.EmbossIr) -> tuple[Source, Diagnostics]: + """Generates Rust source code and definitions for the provided Emboss IR.""" + templates = _load_templates() + diagnostics = [] + + if ir.module: + main_module = ir.module[0] + diagnostics.append( + [ + error.warn( + "", + None, + "Rust backend support is not yet complete and subject to change at any time.", + ) + ] + ) + + imports_list = [] + struct_definitions = [] + module = ir.module[0] + + real_imports = [imp for imp in module.foreign_import if imp.file_name.text != ""] + for imp in real_imports: + crate_name = imp.file_name.text.replace("/", "_").replace(".", "_") + alias = imp.local_name.text + imports_list.append(f"use {crate_name} as {alias};\n") + + for attr in module.attribute: + if not attr.is_default: + if attr.has_field("back_end") and attr.back_end.text != "rust": + continue + if attr.name.text in _SYNTHETIC_ATTRIBUTES: + continue + diagnostics.append( + [ + error.warn( + module.source_file_name, + attr.source_location, + f"Module-level attribute '{attr.name.text}' is not yet supported in this backend. It will be omitted.", + ) + ] + ) + + for type_def in module.type: + struct_definitions.append( + _generate_type(type_def, ir, module, templates, diagnostics) + ) + + rust_source = code_template.format_template( + templates.rust_module, + imports_list="".join(imports_list), + struct_definitions="".join(struct_definitions), + ) + + return rust_source, diagnostics + + +def _is_type_omitted(type_ir) -> bool: + return False + + +def _resolve_type(reference, ir): + target_module_file = reference.canonical_name.module_file + target_object_path = reference.canonical_name.object_path + + for mod in ir.module: + if mod.source_file_name == target_module_file: + for type_def in mod.type: + if type_def.name.name.text == target_object_path[0]: + current = type_def + for path_element in target_object_path[1:]: + found = False + for nested in current.subtype: + if nested.name.name.text == path_element: + current = nested + found = True + break + if not found: + return None + return current + return None + + +def _generate_type(type_ir, ir, module, templates, diagnostics) -> str: + definitions = [] + + type_name = "_".join(type_ir.name.canonical_name.object_path) + + if type_ir.has_field("external"): + return "" + + if type_ir.runtime_parameter: + diagnostics.append( + [ + error.warn( + module.source_file_name, + type_ir.source_location, + f"Parameterized types are not yet supported in this backend. Parameters for '{type_name}' will be omitted.", + ) + ] + ) + + for attr in type_ir.attribute: + if attr.is_default: + continue + if attr.has_field("back_end") and attr.back_end.text != "rust": + continue + if attr.name.text in _SYNTHETIC_ATTRIBUTES: + continue + diagnostics.append( + [ + error.warn( + module.source_file_name, + attr.source_location, + f"Type-level attribute '{attr.name.text}' is not yet supported in this backend. It will be omitted.", + ) + ] + ) + + if type_ir.subtype: + for st in type_ir.subtype: + definitions.append(_generate_type(st, ir, module, templates, diagnostics)) + + if type_ir.has_field("structure"): + definitions.append( + _generate_struct(type_ir, ir, module, templates, diagnostics, type_name) + ) + elif type_ir.has_field("enumeration"): + definitions.append( + _generate_enum(type_ir, ir, module, templates, diagnostics, type_name) + ) + else: + diagnostics.append( + [ + error.warn( + module.source_file_name, + type_ir.source_location, + f"Non-structure/enum types are not yet supported in this backend. Type '{type_name}' will be omitted.", + ) + ] + ) + + return "".join(definitions) + + +def _generate_enum(type_ir, ir, module, templates, diagnostics, enum_name) -> str: + enum_variants = [] + enum_match_variants = [] + enum_aliases = [] + + seen_values = {} + is_signed = False + + for opt in type_ir.enumeration.value: + variant_value = ir_util.constant_value(opt.value) + if variant_value < 0: + is_signed = True + + underlying_type = "i64" if is_signed else "u64" + + for val in type_ir.enumeration.value: + variant_name = val.name.name.text + variant_value = ir_util.constant_value(val.value) + + if variant_value in seen_values: + enum_aliases.append( + code_template.format_template( + templates.enum_alias, + variant_name=variant_name, + original_variant_name=seen_values[variant_value], + ) + ) + else: + seen_values[variant_value] = variant_name + enum_variants.append( + code_template.format_template( + templates.enum_variant, + variant_name=variant_name, + variant_value=str(variant_value), + ) + ) + enum_match_variants.append( + code_template.format_template( + templates.enum_match_variant, + enum_name=enum_name, + variant_name=variant_name, + variant_value=str(variant_value), + ) + ) + + return code_template.format_template( + templates.enum_definition, + enum_name=enum_name, + underlying_type=underlying_type, + enum_variants="".join(enum_variants), + enum_match_variants="".join(enum_match_variants), + enum_aliases="".join(enum_aliases), + ) + + +def _rust_type_for_expr_type(expr_type): + if expr_type.has_field("integer"): + if int(expr_type.integer.minimum_value) < 0: + return "i64" + return "u64" + if expr_type.has_field("boolean"): + return "bool" + if expr_type.has_field("enumeration"): + return "_".join(expr_type.enumeration.name.canonical_name.object_path) + return "u64" + + +def _generate_expression( + expr, ir, module, generated_fields, templates, self_ref="self" +): + if ir_util.is_constant(expr): + return str(ir_util.constant_value(expr)) + + if expr.has_field("boolean_constant"): + return "true" if expr.boolean_constant.value else "false" + + if expr.has_field("field_reference"): + path_names = [ + part.canonical_name.object_path[-1] for part in expr.field_reference.path + ] + if path_names[0] not in generated_fields: + return None + + path_expr = "".join([f".{name}()" for name in path_names]) + return code_template.format_template( + templates.expr_field_reference_no_cast, + self_ref=self_ref, + path_expr=path_expr, + ) + + if expr.has_field("function"): + func = expr.function.function + args = [] + for a in expr.function.args: + arg_str = _generate_expression( + a, ir, module, generated_fields, templates, self_ref + ) + if arg_str is None: + return None + args.append(arg_str) + + if func == ir_data.FunctionMapping.ADDITION.value: + return code_template.format_template( + templates.expr_addition, left=args[0], right=args[1] + ) + if func == ir_data.FunctionMapping.SUBTRACTION.value: + return code_template.format_template( + templates.expr_subtraction, left=args[0], right=args[1] + ) + if func == ir_data.FunctionMapping.MULTIPLICATION.value: + return code_template.format_template( + templates.expr_multiplication, left=args[0], right=args[1] + ) + if func == ir_data.FunctionMapping.MAXIMUM.value: + return code_template.format_template( + templates.expr_maximum, left=args[0], right=args[1] + ) + if func == ir_data.FunctionMapping.CHOICE.value: + return code_template.format_template( + templates.expr_choice, + condition=args[0], + true_value=args[1], + false_value=args[2], + ) + if func == ir_data.FunctionMapping.EQUALITY.value: + return code_template.format_template( + templates.expr_equality, left=args[0], right=args[1] + ) + if func == ir_data.FunctionMapping.LESS.value: + return code_template.format_template( + templates.expr_less, left=args[0], right=args[1] + ) + + return None + return None + + +def _generate_array_field( + field, + field_name, + struct_name, + base_type, + ir, + byte_offset, + byte_length, + const_byte_length, + referenced_type, + source_name, + byte_order, + templates, + field_accessors, + mut_field_accessors, + generated_nested_types, +): + camel_case_field = "".join(word.capitalize() for word in field_name.split("_")) + view_name = struct_name + "_" + camel_case_field + "_ArrayView" + + element_size_in_bits = ir_util.fixed_size_of_type_in_bits(base_type, ir) + if element_size_in_bits is None or element_size_in_bits % 8 != 0: + return + element_size_bytes = element_size_in_bits // 8 + + bits = const_byte_length * 8 + if base_type.has_field("size_in_bits"): + bits = ir_util.constant_value(base_type.size_in_bits) + + storage_type = "S::Sliced<'_>" + storage_type_mut = "S::SlicedMut<'_>" + + if referenced_type.has_field("external"): + element_type = code_template.format_template( + templates.array_element_type_external, + source_name=source_name, + bits=str(bits), + byte_order=byte_order, + storage_type=storage_type, + ) + element_type_mut = code_template.format_template( + templates.array_element_type_external, + source_name=source_name, + bits=str(bits), + byte_order=byte_order, + storage_type=storage_type_mut, + ) + element_constructor = code_template.format_template( + templates.array_element_constructor_external, source_name=source_name + ) + element_constructor_mut = code_template.format_template( + templates.array_element_constructor_external, source_name=source_name + ) + elif referenced_type.has_field("structure"): + element_type = code_template.format_template( + templates.array_element_type_structure, + struct_clean_name=source_name, + storage_type=storage_type, + ) + element_type_mut = code_template.format_template( + templates.array_element_type_structure, + struct_clean_name=source_name + "Mut", + storage_type=storage_type_mut, + ) + element_constructor = code_template.format_template( + templates.array_element_constructor_structure, struct_clean_name=source_name + ) + element_constructor_mut = code_template.format_template( + templates.array_element_constructor_structure, + struct_clean_name=source_name + "Mut", + ) + elif referenced_type.has_field("enumeration"): + element_type = code_template.format_template( + templates.array_element_type_enumeration, + enum_clean_name=source_name, + bits=str(bits), + byte_order=byte_order, + storage_type=storage_type, + ) + element_type_mut = code_template.format_template( + templates.array_element_type_mut_enumeration, + enum_clean_name=source_name, + bits=str(bits), + byte_order=byte_order, + storage_type=storage_type_mut, + ) + element_constructor = code_template.format_template( + templates.array_element_constructor_enumeration + ) + element_constructor_mut = code_template.format_template( + templates.array_element_constructor_mut_enumeration + ) + else: + return + + field_accessors.append( + code_template.format_template( + templates.array_field_accessor, + field_name=field_name, + view_name=view_name, + element_size=str(element_size_bytes), + byte_offset=str(byte_offset), + byte_length=str(byte_length), + ) + ) + mut_field_accessors.append( + code_template.format_template( + templates.array_mut_field_accessor, + field_name=field_name, + view_name=view_name, + element_size=str(element_size_bytes), + byte_offset=str(byte_offset), + byte_length=str(byte_length), + ) + ) + generated_nested_types.append( + code_template.format_template( + templates.array_view_struct, + view_name=view_name, + element_size=str(element_size_bytes), + element_type=element_type, + element_constructor=element_constructor, + element_type_mut=element_type_mut, + element_constructor_mut=element_constructor_mut, + ) + ) + + +def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) -> str: + field_accessors = [] + mut_field_accessors = [] + generated_nested_types = [] + generated_fields = set() + + fields_to_process = [] + if type_ir.structure.fields_in_dependency_order: + for idx in type_ir.structure.fields_in_dependency_order: + fields_to_process.append(type_ir.structure.field[idx]) + else: + fields_to_process = type_ir.structure.field + + for field in fields_to_process: + field_name = field.name.name.text + + # Skip synthetic fields like $size_in_bytes for now + if field_name.startswith("$"): + continue + + if field.has_field("existence_condition") and not field.has_field( + "read_transform" + ): + cond = ir_util.constant_value(field.existence_condition) + if cond is not True: + diagnostics.append( + [ + error.warn( + module.source_file_name, + field.source_location, + f"Conditional fields are not yet supported in this backend. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + + if not field.has_field("location"): + if not field.has_field("read_transform"): + diagnostics.append( + [ + error.warn( + module.source_file_name, + ( + field.type.source_location + if field.has_field("type") + else field.source_location + ), + f"Virtual fields without read_transform are not supported. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + + if field.read_transform.has_field( + "type" + ) and field.read_transform.type.has_field("atomic_type"): + orig = [ + p.text + for p in field.read_transform.type.atomic_type.reference.source_name + ] + if "::".join(orig) in _UNSUPPORTED_PRELUDE_TYPES: + diagnostics.append( + [ + error.warn( + module.source_file_name, + field.source_location, + f"Virtual field '{field_name}' resolves to unsupported type. It will be omitted.", + ) + ] + ) + continue + + expr_str = _generate_expression( + field.read_transform, ir, module, generated_fields, templates + ) + if expr_str is None: + diagnostics.append( + [ + error.warn( + module.source_file_name, + field.read_transform.source_location, + f"Virtual field '{field_name}' uses unsupported expression. It will be omitted.", + ) + ] + ) + continue + + return_type = _rust_type_for_expr_type(field.read_transform.type) + + if field.read_transform.has_field( + "field_reference" + ) and field.read_transform.type.has_field("opaque"): + # opaque fields usually mean it's an alias to a struct/array view. + # Since we don't map complex return types for aliases yet, skip properly. + diagnostics.append( + [ + error.warn( + module.source_file_name, + field.source_location, + f"Virtual field '{field_name}' aliasing a complex type is not yet supported. It will be omitted.", + ) + ] + ) + continue + else: + if return_type == "bool": + accessor_template = templates.virtual_bool_field_accessor + mut_accessor_template = templates.mut_virtual_bool_field_accessor + else: + accessor_template = templates.virtual_field_accessor + mut_accessor_template = templates.mut_virtual_field_accessor + + field_accessors.append( + code_template.format_template( + accessor_template, + field_name=field_name, + return_type=return_type, + expression=expr_str, + ) + ) + mut_field_accessors.append( + code_template.format_template( + mut_accessor_template, + field_name=field_name, + return_type=return_type, + expression=expr_str, + ) + ) + + generated_fields.add(field_name) + continue + + if not field.has_field("type") or not ( + field.type.has_field("atomic_type") or field.type.has_field("array_type") + ): + loc = ( + field.type.source_location + if field.has_field("type") + else field.source_location + ) + diagnostics.append( + [ + error.warn( + module.source_file_name, + loc, + f"Non-atomic and non-array types are not yet supported in this backend. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + + is_array = field.type.has_field("array_type") + if is_array: + base_type = field.type.array_type.base_type + else: + base_type = field.type + + if not base_type.has_field("atomic_type"): + diagnostics.append( + [ + error.warn( + module.source_file_name, + field.source_location, + f"Arrays of non-atomic types (e.g. multi-dimensional arrays) are not yet supported in this backend. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + + orig_source_name = [ + part.text for part in base_type.atomic_type.reference.source_name + ] + obj_path = [ + part for part in base_type.atomic_type.reference.canonical_name.object_path + ] + + num_module_parts = len(orig_source_name) - len(obj_path) + if num_module_parts > 0: + module_prefix = "::".join(orig_source_name[:num_module_parts]) + "::" + type_name = "_".join(obj_path) + source_name = module_prefix + type_name + source_name_for_prelude = "::".join( + orig_source_name + ) # original style for checking preludes + else: + source_name = "_".join(obj_path) + source_name_for_prelude = "::".join(orig_source_name) + if source_name_for_prelude == "Flag": + source_name = "UInt" + if source_name_for_prelude in _UNSUPPORTED_PRELUDE_TYPES: + diagnostics.append( + [ + error.warn( + module.source_file_name, + field.source_location, + f"Type '{source_name_for_prelude}' is not yet supported in this backend. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + + target_type = _resolve_type(base_type.atomic_type.reference, ir) + + byte_offset_expr = _generate_expression( + field.location.start, ir, module, generated_fields, templates + ) + byte_length_expr = _generate_expression( + field.location.size, ir, module, generated_fields, templates + ) + + if byte_offset_expr is None or byte_length_expr is None: + reason = "offset" if byte_offset_expr is None else "size" + diagnostics.append( + [ + error.warn( + module.source_file_name, + field.source_location, + f"Non-constant {reason} relies on unsupported expression or omitted field. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + + unhandled_attr = None + for attr in field.attribute: + if attr.is_default: + continue + if attr.has_field("back_end") and attr.back_end.text != "rust": + continue + + attr_name = attr.name.text + if attr_name == "requires": + diagnostics.append( + [ + error.warn( + module.source_file_name, + attr.source_location, + f"Validation '{attr_name}' is not yet supported in this backend. Validation will not be generated for field '{field_name}'.", + ) + ] + ) + elif attr_name != "byte_order": + unhandled_attr = attr + break + + if unhandled_attr: + diagnostics.append( + [ + error.warn( + module.source_file_name, + unhandled_attr.source_location, + f"Attribute '{unhandled_attr.name.text}' is not yet supported in this backend. It will be ignored for field '{field_name}'.", + ) + ] + ) + + byte_order_attr = ir_util.get_attribute(field.attribute, "byte_order") + if byte_order_attr: + byte_order = byte_order_attr.string_constant.text + else: + byte_order = "Null" + + byte_offset = byte_offset_expr + byte_length = byte_length_expr + + if ir_util.is_constant(field.location.size): + const_byte_length = ir_util.constant_value(field.location.size) + else: + const_byte_length = 0 + + referenced_type = ir_util.find_object(base_type.atomic_type.reference, ir) + + if is_array: + # Generate a unique struct for each array field. + _generate_array_field( + field, + field_name, + struct_name, + base_type, + ir, + byte_offset, + byte_length, + const_byte_length, + referenced_type, + source_name, + byte_order, + templates, + field_accessors, + mut_field_accessors, + generated_nested_types, + ) + else: + if referenced_type.has_field("external"): + bits = const_byte_length * type_ir.addressable_unit + if field.type.has_field("size_in_bits"): + bits = ir_util.constant_value(field.type.size_in_bits) + + accessor_template = templates.external_field_accessor + mut_accessor_template = templates.external_mut_field_accessor + + if type_ir.addressable_unit == 1: + bit_offset_int = int(ir_util.constant_value(field.location.start)) + bits_int = int(bits) + byte_len_int = ((bit_offset_int + bits_int - 1) // 8) + 1 + bit_offset_str = str(bit_offset_int) + + field_accessors.append( + code_template.format_template( + templates.bit_external_field_accessor, + field_name=field_name, + type_name=source_name, + bits=str(bits), + byte_order=byte_order, + bit_offset=bit_offset_str, + byte_length=str(byte_len_int), + ) + ) + mut_field_accessors.append( + code_template.format_template( + templates.bit_external_mut_field_accessor, + field_name=field_name, + type_name=source_name, + bits=str(bits), + byte_order=byte_order, + bit_offset=bit_offset_str, + byte_length=str(byte_len_int), + ) + ) + else: + field_accessors.append( + code_template.format_template( + templates.external_field_accessor, + field_name=field_name, + type_name=source_name, + bits=str(bits), + byte_order=byte_order, + byte_offset=str(byte_offset), + byte_length=str(byte_length), + ) + ) + mut_field_accessors.append( + code_template.format_template( + templates.external_mut_field_accessor, + field_name=field_name, + type_name=source_name, + bits=str(bits), + byte_order=byte_order, + byte_offset=str(byte_offset), + byte_length=str(byte_length), + ) + ) + elif referenced_type.has_field("structure"): + field_accessors.append( + code_template.format_template( + templates.struct_field_accessor, + field_name=field_name, + type_name=source_name, + byte_offset=str(byte_offset), + byte_length=str(byte_length), + ) + ) + mut_field_accessors.append( + code_template.format_template( + templates.struct_mut_field_accessor, + field_name=field_name, + type_name=source_name, + byte_offset=str(byte_offset), + byte_length=str(byte_length), + ) + ) + elif referenced_type.has_field("enumeration"): + bits = const_byte_length * type_ir.addressable_unit + if field.type.has_field("size_in_bits"): + bits = ir_util.constant_value(field.type.size_in_bits) + if type_ir.addressable_unit == 1: + bit_offset_int = int(ir_util.constant_value(field.location.start)) + bits_int = int(bits) + byte_len_int = ((bit_offset_int + bits_int - 1) // 8) + 1 + field_accessors.append( + code_template.format_template( + templates.bit_enum_field_accessor, + field_name=field_name, + enum_name=source_name, + bits=str(bits), + byte_order=byte_order, + bit_offset=str(bit_offset_int), + byte_length=str(byte_len_int), + ) + ) + mut_field_accessors.append( + code_template.format_template( + templates.bit_enum_mut_field_accessor, + field_name=field_name, + enum_name=source_name, + bits=str(bits), + byte_order=byte_order, + bit_offset=str(bit_offset_int), + byte_length=str(byte_len_int), + ) + ) + else: + field_accessors.append( + code_template.format_template( + templates.enum_field_accessor, + field_name=field_name, + enum_name=source_name, + bits=str(bits), + byte_order=byte_order, + byte_offset=str(byte_offset), + byte_length=str(byte_length), + ) + ) + mut_field_accessors.append( + code_template.format_template( + templates.enum_mut_field_accessor, + field_name=field_name, + enum_name=source_name, + bits=str(bits), + byte_order=byte_order, + byte_offset=str(byte_offset), + byte_length=str(byte_length), + ) + ) + else: + diagnostics.append( + [ + error.warn( + module.source_file_name, + ( + field.type.source_location + if field.has_field("type") + else field.source_location + ), + f"Target type variety is not yet supported in this backend. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + + generated_fields.add(field_name) + + main_struct_def = code_template.format_template( + templates.struct_view, + struct_name=struct_name, + field_accessors="".join(field_accessors), + mut_field_accessors="".join(mut_field_accessors), + ) + return "".join(generated_nested_types) + main_struct_def + + +def generate_code_and_log_errors( + ir: ir_data.EmbossIr, color_output: ColorOutput +) -> tuple[Source, ErrorList]: + """Generates Rust source code and logs any resulting errors or warnings.""" + rust_source, diagnostics = generate_code(ir) + + if diagnostics: + _show_errors(diagnostics, ir, color_output) + + errors = [ + msg_list + for msg_list in diagnostics + if any(msg.severity == error.ERROR for msg in msg_list) + ] + + return rust_source, errors + + +def _parse_command_line(argv): + parser = argparse.ArgumentParser(description="Emboss Rust code generator") + parser.add_argument( + "--input-file", type=str, help="Path to input IR", required=True + ) + parser.add_argument( + "--output-file", type=str, help="Path to output rust file", required=True + ) + parser.add_argument("--color-output", type=str, default="auto") + return parser.parse_args(argv[1:]) + + +def main(flags): + with open(flags.input_file) as f: + ir = ir_data_utils.IrDataSerializer.from_json(ir_data.EmbossIr, f.read()) + + rust_source, errors = generate_code_and_log_errors(ir, flags.color_output) + if errors: + return 1 + + with open(flags.output_file, "w") as f: + f.write(rust_source) + return 0 + + +if __name__ == "__main__": + sys.exit(main(_parse_command_line(sys.argv))) diff --git a/compiler/back_end/experimental/rust/generated_code_templates b/compiler/back_end/experimental/rust/generated_code_templates new file mode 100644 index 00000000..3346a2cc --- /dev/null +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -0,0 +1,400 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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. + +// ** rust_module ** /////////////////////////////////////////////////////////// +// Generated by the Emboss compiler. DO NOT EDIT! +#![allow(dead_code, unused_imports, unused_variables, non_camel_case_types, unused_parens)] + +use emboss_runtime::prelude::*; +pub use emboss_runtime::{Error, Storage, MutStorage, TryRead, TryWrite, UInt, Int, VirtualField}; + +${imports_list} + +${struct_definitions} + +// ** struct_view ** // +pub struct ${struct_name} { + storage: S, +} + +impl ${struct_name} { + pub fn new(storage: S) -> Self { + Self { storage } + } +${field_accessors} +} + +pub struct ${struct_name}Mut { + storage: S, +} + +impl ${struct_name}Mut { + pub fn new(storage: S) -> Self { + Self { storage } + } +${mut_field_accessors} +} + +// ** bit_external_field_accessor ** // + pub fn ${field_name}(&self) -> emboss_runtime::Bit${type_name}<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>> { + let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { + let offset = 0; + let len = (${byte_length}) as usize; // Total struct size in bytes + core::result::Result::Ok((offset, len)) + })(); + let storage = match offset_res { + core::result::Result::Ok((offset, len)) => self.storage.slice(offset, len), + core::result::Result::Err(e) => core::result::Result::Err(e), + }; + emboss_runtime::Bit${type_name}::<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>::new(storage) + } + +// ** bit_external_mut_field_accessor ** // + pub fn ${field_name}(&mut self) -> emboss_runtime::Bit${type_name}<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>> { + let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { + let offset = 0; + let len = (${byte_length}) as usize; + core::result::Result::Ok((offset, len)) + })(); + let storage = match offset_res { + core::result::Result::Ok((offset, len)) => self.storage.slice_mut(offset, len), + core::result::Result::Err(e) => core::result::Result::Err(e), + }; + emboss_runtime::Bit${type_name}::<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>::new(storage) + } + +// ** external_field_accessor ** // + pub fn ${field_name}(&self) -> ${type_name}<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>> { + let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; + core::result::Result::Ok((offset, len)) + })(); + let storage = match offset_res { + core::result::Result::Ok((offset, len)) => self.storage.slice(offset, len), + core::result::Result::Err(e) => core::result::Result::Err(e), + }; + ${type_name}::<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>::new(storage) + } + +// ** external_mut_field_accessor ** // + pub fn ${field_name}(&mut self) -> ${type_name}<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>> { + let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; + core::result::Result::Ok((offset, len)) + })(); + let storage = match offset_res { + core::result::Result::Ok((offset, len)) => self.storage.slice_mut(offset, len), + core::result::Result::Err(e) => core::result::Result::Err(e), + }; + ${type_name}::<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>::new(storage) + } + +// ** struct_field_accessor ** // + pub fn ${field_name}(&self) -> ${type_name}, emboss_runtime::Error>> { + let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; + core::result::Result::Ok((offset, len)) + })(); + let storage = match offset_res { + core::result::Result::Ok((offset, len)) => self.storage.slice(offset, len), + core::result::Result::Err(e) => core::result::Result::Err(e), + }; + ${type_name}::new(storage) + } + +// ** struct_mut_field_accessor ** // + pub fn ${field_name}(&mut self) -> ${type_name}Mut, emboss_runtime::Error>> { + let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; + core::result::Result::Ok((offset, len)) + })(); + let storage = match offset_res { + core::result::Result::Ok((offset, len)) => self.storage.slice_mut(offset, len), + core::result::Result::Err(e) => core::result::Result::Err(e), + }; + ${type_name}Mut::new(storage) + } + +// ** enum_definition ** // +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(${underlying_type})] +pub enum ${enum_name} { +${enum_variants} +} + +impl core::convert::From<${enum_name}> for u8 { fn from(val: ${enum_name}) -> Self { val as u8 } } +impl core::convert::From<${enum_name}> for u16 { fn from(val: ${enum_name}) -> Self { val as u16 } } +impl core::convert::From<${enum_name}> for u32 { fn from(val: ${enum_name}) -> Self { val as u32 } } +impl core::convert::From<${enum_name}> for u64 { fn from(val: ${enum_name}) -> Self { val as u64 } } +impl core::convert::From<${enum_name}> for i8 { fn from(val: ${enum_name}) -> Self { val as i8 } } +impl core::convert::From<${enum_name}> for i16 { fn from(val: ${enum_name}) -> Self { val as i16 } } +impl core::convert::From<${enum_name}> for i32 { fn from(val: ${enum_name}) -> Self { val as i32 } } +impl core::convert::From<${enum_name}> for i64 { fn from(val: ${enum_name}) -> Self { val as i64 } } + +impl + core::marker::Copy> emboss_runtime::TryFromRaw for ${enum_name} { + fn try_from_raw(val: V) -> core::result::Result> { + let v_raw: ${underlying_type} = val.into(); + match v_raw { +${enum_match_variants} + _ => Err(emboss_runtime::Error::UnknownEnum(val)), + } + } +} + +impl ${enum_name} { +${enum_aliases} +} + +// ** enum_variant ** // + ${variant_name} = ${variant_value}, + +// ** enum_alias ** // + pub const ${variant_name}: Self = Self::${original_variant_name}; + +// ** enum_match_variant ** // + ${variant_value} => Ok(${enum_name}::${variant_name}), + +// ** expr_addition ** // +( (${left}) as i64 + (${right}) as i64 ) + +// ** expr_subtraction ** // +( (${left}) as i64 - (${right}) as i64 ) + +// ** expr_multiplication ** // +( (${left}) as i64 * (${right}) as i64 ) + +// ** expr_constant ** // +${constant_value} + +// ** expr_field_reference ** // +{ let val = ${self_ref}.${field_name}().try_read().map_err(|e| match e { emboss_runtime::Error::OutOfBounds => emboss_runtime::Error::OutOfBounds, emboss_runtime::Error::UnknownEnum(_) => emboss_runtime::Error::UnknownEnum(()) })?; val as usize } + +// ** expr_maximum ** // +core::cmp::max((${left}) as i64, (${right}) as i64) + +// ** expr_minimum ** // +core::cmp::min((${left}) as i64, (${right}) as i64) + +// ** expr_choice ** // +(if ${condition} { ${true_value} } else { ${false_value} }) + + +// ** enum_field_accessor ** // + pub fn ${field_name}(&self) -> emboss_runtime::EnumView<${enum_name}, emboss_runtime::UInt<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>> { + let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; + core::result::Result::Ok((offset, len)) + })(); + let storage = match offset_res { + core::result::Result::Ok((offset, len)) => self.storage.slice(offset, len), + core::result::Result::Err(e) => core::result::Result::Err(e), + }; + emboss_runtime::EnumView::new(emboss_runtime::UInt::new(storage)) + } + +// ** enum_mut_field_accessor ** // + pub fn ${field_name}(&mut self) -> emboss_runtime::EnumViewMut<${enum_name}, emboss_runtime::UInt<${bits}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>> { + let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; + core::result::Result::Ok((offset, len)) + })(); + let storage = match offset_res { + core::result::Result::Ok((offset, len)) => self.storage.slice_mut(offset, len), + core::result::Result::Err(e) => core::result::Result::Err(e), + }; + emboss_runtime::EnumViewMut::new(emboss_runtime::UInt::new(storage)) + } + +// ** array_field_accessor ** // + pub fn ${field_name}(&self) -> core::result::Result<${view_name}>, emboss_runtime::Error> { + let offset_res = (|| -> core::result::Result<(usize, usize, usize), emboss_runtime::Error> { + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; + let element_count = (len / (${element_size})) as usize; + core::result::Result::Ok((offset, len, element_count)) + })(); + match offset_res { + core::result::Result::Ok((offset, len, element_count)) => { + let storage = self.storage.slice(offset, len)?; + core::result::Result::Ok(${view_name}::new(storage, element_count)) + }, + core::result::Result::Err(e) => core::result::Result::Err(e), + } + } + +// ** array_mut_field_accessor ** // + pub fn ${field_name}_mut(&mut self) -> core::result::Result<${view_name}Mut>, emboss_runtime::Error> { + let offset_res = (|| -> core::result::Result<(usize, usize, usize), emboss_runtime::Error> { + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; + let element_count = (len / (${element_size})) as usize; + core::result::Result::Ok((offset, len, element_count)) + })(); + match offset_res { + core::result::Result::Ok((offset, len, element_count)) => { + let storage = self.storage.slice_mut(offset, len)?; + core::result::Result::Ok(${view_name}Mut::new(storage, element_count)) + }, + core::result::Result::Err(e) => core::result::Result::Err(e), + } + } + +// ** array_view_struct ** // +pub struct ${view_name} { + storage: S, + element_count: usize, +} + +impl ${view_name} { + pub const ELEMENT_SIZE_BYTES: usize = ${element_size}; + pub fn new(storage: S, element_count: usize) -> Self { + Self { storage, element_count } + } + pub fn element_count(&self) -> usize { + self.element_count + } + pub fn get(&self, index: usize) -> core::result::Result<${element_type}, emboss_runtime::Error> { + if index >= self.element_count { + return core::result::Result::Err(emboss_runtime::Error::OutOfBounds); + } + let offset = index * Self::ELEMENT_SIZE_BYTES; + let storage = self.storage.slice(offset, Self::ELEMENT_SIZE_BYTES)?; + core::result::Result::Ok(${element_constructor}) + } +} + +pub struct ${view_name}Mut { + storage: S, + element_count: usize, +} + +impl ${view_name}Mut { + pub const ELEMENT_SIZE_BYTES: usize = ${element_size}; + pub fn new(storage: S, element_count: usize) -> Self { + Self { storage, element_count } + } + pub fn element_count(&self) -> usize { + self.element_count + } + pub fn get_mut(&mut self, index: usize) -> core::result::Result<${element_type_mut}, emboss_runtime::Error> { + if index >= self.element_count { + return core::result::Result::Err(emboss_runtime::Error::OutOfBounds); + } + let offset = index * Self::ELEMENT_SIZE_BYTES; + let storage = self.storage.slice_mut(offset, Self::ELEMENT_SIZE_BYTES)?; + core::result::Result::Ok(${element_constructor_mut}) + } +} + +// ** array_element_type_external ** // +emboss_runtime::${source_name}<${bits}, emboss_runtime::${byte_order}, ${storage_type}> + +// ** array_element_constructor_external ** // +emboss_runtime::${source_name}::new(storage) + +// ** array_element_type_structure ** // +${struct_clean_name}<${storage_type}> + +// ** array_element_constructor_structure ** // +${struct_clean_name}::new(storage) + +// ** array_element_type_enumeration ** // +emboss_runtime::EnumView<${enum_clean_name}, ${bits}, emboss_runtime::${byte_order}, ${storage_type}> + +// ** array_element_constructor_enumeration ** // +emboss_runtime::EnumView::new(emboss_runtime::UInt::new(storage)) + +// ** array_element_type_mut_enumeration ** // +emboss_runtime::EnumViewMut<${enum_clean_name}, ${bits}, emboss_runtime::${byte_order}, ${storage_type}> + +// ** array_element_constructor_mut_enumeration ** // +emboss_runtime::EnumViewMut::new(emboss_runtime::UInt::new(storage)) + + + +// ** expr_field_reference_no_cast ** // +({ let val = ${self_ref}${path_expr}.try_read().map_err(|e| match e { emboss_runtime::Error::OutOfBounds => emboss_runtime::Error::OutOfBounds, emboss_runtime::Error::UnknownEnum(_) => emboss_runtime::Error::UnknownEnum(()) })?; val }) + +// ** expr_equality ** // +(${left} == ${right}) + +// ** expr_less ** // +(${left} < ${right}) + +// ** virtual_field_accessor ** // + pub fn ${field_name}(&self) -> emboss_runtime::VirtualField<${return_type}> { + let value = (|| -> core::result::Result<${return_type}, emboss_runtime::Error> { + core::result::Result::Ok( (${expression}) as ${return_type} ) + })(); + emboss_runtime::VirtualField::new(value) + } + +// ** mut_virtual_field_accessor ** // + pub fn ${field_name}(&mut self) -> emboss_runtime::VirtualField<${return_type}> { + let value = (|| -> core::result::Result<${return_type}, emboss_runtime::Error> { + core::result::Result::Ok( (${expression}) as ${return_type} ) + })(); + emboss_runtime::VirtualField::new(value) + } + +// ** virtual_bool_field_accessor ** // + pub fn ${field_name}(&self) -> emboss_runtime::VirtualField { + let value = (|| -> core::result::Result { + core::result::Result::Ok( (${expression}) != 0 ) + })(); + emboss_runtime::VirtualField::new(value) + } + +// ** mut_virtual_bool_field_accessor ** // + pub fn ${field_name}(&mut self) -> emboss_runtime::VirtualField { + let value = (|| -> core::result::Result { + core::result::Result::Ok( (${expression}) != 0 ) + })(); + emboss_runtime::VirtualField::new(value) + } + +// ** bit_enum_field_accessor ** // + pub fn ${field_name}(&self) -> emboss_runtime::EnumView<${enum_name}, emboss_runtime::BitUInt<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>> { + let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { + let offset = 0; + let len = (${byte_length}) as usize; + core::result::Result::Ok((offset, len)) + })(); + let storage = match offset_res { + core::result::Result::Ok((offset, len)) => self.storage.slice(offset, len), + core::result::Result::Err(e) => core::result::Result::Err(e), + }; + emboss_runtime::EnumView::new(emboss_runtime::BitUInt::new(storage)) + } + +// ** bit_enum_mut_field_accessor ** // + pub fn ${field_name}(&mut self) -> emboss_runtime::EnumViewMut<${enum_name}, emboss_runtime::BitUInt<${bits}, ${bit_offset}, emboss_runtime::${byte_order}, core::result::Result, emboss_runtime::Error>>> { + let offset_res = (|| -> core::result::Result<(usize, usize), emboss_runtime::Error> { + let offset = 0; + let len = (${byte_length}) as usize; + core::result::Result::Ok((offset, len)) + })(); + let storage = match offset_res { + core::result::Result::Ok((offset, len)) => self.storage.slice_mut(offset, len), + core::result::Result::Err(e) => core::result::Result::Err(e), + }; + emboss_runtime::EnumViewMut::new(emboss_runtime::BitUInt::new(storage)) + } + diff --git a/compiler/back_end/experimental/rust/testcode/BUILD b/compiler/back_end/experimental/rust/testcode/BUILD new file mode 100644 index 00000000..6ae2fe90 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/BUILD @@ -0,0 +1,91 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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. + +load("@rules_rust//rust:defs.bzl", "rust_test") +load("//:build_defs.bzl", "emboss_rust_library") +rust_test( + name = "imported_test", + srcs = ["imported_test.rs"], + deps = [ + "//runtime/experimental/rust:emboss_runtime", + "//testdata:imported_emb_rs", + ], +) + +rust_test( + name = "importer_test", + srcs = ["importer_test.rs"], + deps = [ + "//runtime/experimental/rust:emboss_runtime", + "//testdata:imported_emb_rs", + "//testdata:importer_emb_rs", + ], +) + +rust_test( + name = "next_keyword_test", + srcs = ["next_keyword_test.rs"], + deps = [ + "//runtime/experimental/rust:emboss_runtime", + "//testdata:next_keyword_emb_rs", + ], +) + +rust_test( + name = "nested_structure_test", + srcs = ["nested_structure_test.rs"], + deps = [ + "//testdata:nested_structure_emb_rs", + "//runtime/experimental/rust:emboss_runtime", + ], +) +rust_test( + name = "endian_test", + srcs = ["endian_test.rs"], + deps = [ + "//testdata:endian_emb_rs", + "//runtime/experimental/rust:emboss_runtime", + ], +) +rust_test( + name = "int_sizes_test", + srcs = ["int_sizes_test.rs"], + deps = [ + "//testdata:int_sizes_emb_rs", + ], +) +rust_test( + name = "enum_test", + srcs = ["enum_test.rs"], + deps = [ + "//runtime/experimental/rust:emboss_runtime", + "//testdata:enum_emb_rs", + ], +) +rust_test( + name = "dynamic_size_test", + srcs = ["dynamic_size_test.rs"], + deps = [ + "//runtime/experimental/rust:emboss_runtime", + "//testdata:dynamic_size_emb_rs", + ], +) +rust_test( + name = "auto_array_size_test", + srcs = ["auto_array_size_test.rs"], + deps = [ + "//runtime/experimental/rust:emboss_runtime", + "//testdata:auto_array_size_emb_rs", + ], +) diff --git a/compiler/back_end/experimental/rust/testcode/auto_array_size_test.rs b/compiler/back_end/experimental/rust/testcode/auto_array_size_test.rs new file mode 100644 index 00000000..8de84312 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/auto_array_size_test.rs @@ -0,0 +1,42 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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. + +use testdata_auto_array_size_emb::*; + +#[test] +fn test_array_access() { + let mut storage = vec![0u8; 22]; // Large enough for all arrays with a=3 + + // Set array_size to something valid, e.g., 3 + storage[0] = 3; // a = 3 => array_size = 3 + + let view = AutoSize::new(&storage[..]); + // Validate the dynamic struct array size offsets + // array_size is 'a' which is 3 + // byte_offset = 13 + 3 = 16 + // byte_length = a * 2 = 6 + // element_size = 2. element_count = byte_length / 2 = 3. + assert_eq!(view.array_size().try_read().unwrap(), 3); + + let dyn_struct_array = view.dynamic_struct_array().unwrap(); + assert_eq!(dyn_struct_array.element_count(), 3); // 6 bytes / 2 bytes per struct = 3 structs! + + // let's try getting index 0 + let element0 = dyn_struct_array.get(0).unwrap(); + assert_eq!(element0.a().try_read().unwrap(), 0); + + // get index 3 which is OutOfBounds because count is 3 (valid indices are 0, 1, 2) + let res = dyn_struct_array.get(3); + assert!(res.is_err()); +} diff --git a/compiler/back_end/experimental/rust/testcode/dynamic_size_test.rs b/compiler/back_end/experimental/rust/testcode/dynamic_size_test.rs new file mode 100644 index 00000000..7ed09191 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/dynamic_size_test.rs @@ -0,0 +1,67 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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. + +pub use testdata_dynamic_size_emb::*; +use emboss_runtime::{prelude::*, Error}; + +#[test] +fn test_chained_size_in_order() { + let bytes = [0x01, 0x02, 0x03, 0x04]; + let view = ChainedSize::new(&bytes[..]); + + // a is at 0 -> 1 + assert_eq!(view.a().try_read(), Ok(1)); + // b is at a (1) -> 2 + assert_eq!(view.b().try_read(), Ok(2)); + // c is at b (2) -> 3 + assert_eq!(view.c().try_read(), Ok(3)); + // d is at c (3) -> 4 + assert_eq!(view.d().try_read(), Ok(4)); +} + +#[test] +fn test_chained_size_mut() { + let mut bytes = [0x01, 0x02, 0x03, 0x04]; + let mut view = ChainedSizeMut::new(&mut bytes[..]); + + assert_eq!(view.a().try_read(), Ok(1)); + view.b().try_write(5).unwrap(); + + assert_eq!(bytes[1], 5); +} + +#[test] +fn test_negative_term_in_location_valid() { + let mut bytes = [0u8; 16]; + bytes[0] = 5; // a = 5 + bytes[5] = 42; // b at 10 - 5 = 5 + + let view = NegativeTermInLocation::new(&bytes[..]); + assert_eq!(view.a().try_read(), Ok(5)); + assert_eq!(view.b().try_read(), Ok(42)); +} + +#[test] +fn test_dynamic_overlap() { + let mut bytes = [0u8; 16]; + bytes[0] = 4; // a = 4 + bytes[9] = 2; // b = 2 + bytes[4] = 7; // c at 4 + bytes[5] = 8; // d at a+1 = 5 + + let view = DynamicFinalFieldOverlaps::new(&bytes[..]); + assert_eq!(view.a().try_read(), Ok(4)); + assert_eq!(view.b().try_read(), Ok(2)); + assert_eq!(view.d().try_read(), Ok(8)); +} diff --git a/compiler/back_end/experimental/rust/testcode/endian_test.rs b/compiler/back_end/experimental/rust/testcode/endian_test.rs new file mode 100644 index 00000000..8081b940 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/endian_test.rs @@ -0,0 +1,78 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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. + +use emboss_runtime::Error; + +#[test] +fn reads_endian_values_correctly() { + let container: &[u8] = &[ + 0x78, 0x56, 0x34, 0x12, // 0:4 little_uint32 == 0x12345678 (Little Endian) + 0x12, 0x34, 0x56, 0x78, // 4:8 big_uint32 == 0x12345678 (Big Endian) + 0xAB, // 8:9 single_byte == 0xAB + ]; + + let view = testdata_endian_emb::Message::new(container); + + let little_val: u32 = view.little_uint32().try_read().unwrap(); + assert_eq!(little_val, 0x12345678); + + let big_val: u32 = view.big_uint32().try_read().unwrap(); + assert_eq!(big_val, 0x12345678); + + let byte_val: u8 = view.single_byte().try_read().unwrap(); + assert_eq!(byte_val, 0xAB); +} + +#[test] +fn handles_endian_out_of_bounds() { + let container: &[u8] = &[ + 0x78, 0x56, 0x34, 0x12, // 0:4 little_uint32 == 0x12345678 + 0x12, 0x34, 0x56, // TRUNCATED 4:7 + ]; + + let view = testdata_endian_emb::Message::new(container); + + let little_val: u32 = view.little_uint32().try_read().unwrap(); + assert_eq!(little_val, 0x12345678); + + assert_eq!(view.big_uint32().try_read(), Err(Error::OutOfBounds)); + assert_eq!(view.single_byte().try_read(), Err(Error::OutOfBounds)); +} + +#[test] +fn writes_endian_values_correctly() { + let mut container: [u8; 9] = [0; 9]; + { + let mut view = testdata_endian_emb::MessageMut::new(&mut container[..]); + view.little_uint32().try_write(0x12345678).unwrap(); + view.big_uint32().try_write(0x12345678).unwrap(); + view.single_byte().try_write(0xAB).unwrap(); + } + let expected: &[u8] = &[ + 0x78, 0x56, 0x34, 0x12, // 0:4 little_uint32 == 0x12345678 (Little Endian) + 0x12, 0x34, 0x56, 0x78, // 4:8 big_uint32 == 0x12345678 (Big Endian) + 0xAB, // 8:9 single_byte == 0xAB + ]; + assert_eq!(&container[..], expected); +} + +#[test] +fn handles_endian_write_out_of_bounds() { + let mut container: [u8; 7] = [0; 7]; + let mut view = testdata_endian_emb::MessageMut::new(&mut container[..]); + + assert!(view.little_uint32().try_write(0x12345678).is_ok()); + assert_eq!(view.big_uint32().try_write(0x12345678), Err(Error::OutOfBounds)); + assert_eq!(view.single_byte().try_write(0xAB), Err(Error::OutOfBounds)); +} diff --git a/compiler/back_end/experimental/rust/testcode/enum_test.rs b/compiler/back_end/experimental/rust/testcode/enum_test.rs new file mode 100644 index 00000000..4cc52548 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/enum_test.rs @@ -0,0 +1,77 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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. + +pub use testdata_enum_emb::*; + +#[test] +fn generates_correct_enum_variants() { + assert_eq!(Kind::WIDGET as u64, 0); + assert_eq!(Kind::SPROCKET as u64, 1); + assert_eq!(Kind::GEEGAW as u64, 2); + assert_eq!( + Kind::COMPUTED as u64, + Kind::GEEGAW as u64 + Kind::SPROCKET as u64 + ); + assert_eq!(Kind::MAX32BIT as u64, 4294967295); + assert_eq!(Kind::LARGE_VALUE as u64, 2000); + assert_eq!(Kind::DUPLICATE_LARGE_VALUE as u64, 2000); +} + +#[test] +fn can_read_kind() { + let k_manifest_entry: [u8; 14] = [ + 0x01, // 0:1 Kind kind == SPROCKET + 0x04, 0x00, 0x00, 0x00, // 1:5 UInt count == 4 + 0x02, 0x00, 0x00, 0x00, // 5:9 Kind wide_kind == GEEGAW + 0x20, 0x00, 0x00, 0x00, 0x00, // 9:14 Kind wide_kind_in_bits == GEEGAW + ]; + let view = ManifestEntry::new(&k_manifest_entry); + + assert_eq!(view.kind().try_read().unwrap(), Kind::SPROCKET); + assert_eq!(view.count().try_read().unwrap(), 4); + assert_eq!(view.wide_kind().try_read().unwrap(), Kind::GEEGAW); +} + +#[test] +fn edge_cases_unknown_enum() { + let k_manifest_entry_edge_cases: [u8; 14] = [ + 0xff, // 0:1 Kind kind == 0xff + 0x04, 0x00, 0x00, 0x00, // 1:5 UInt count == 4 + 0xff, 0xff, 0xff, 0xff, // 5:9 Kind wide_kind == MAX32BIT + 0xf0, 0xff, 0xff, 0xff, 0x0f, // 9:14 Kind wide_kind_in_bits == GEEGAW + ]; + let view = ManifestEntry::new(&k_manifest_entry_edge_cases); + + assert_eq!(view.kind().try_read().unwrap_err(), emboss_runtime::Error::UnknownEnum(255)); + assert_eq!(view.count().try_read().unwrap(), 4); + assert_eq!(view.wide_kind().try_read().unwrap(), Kind::MAX32BIT); +} + +#[test] +fn can_write_kind() { + let mut buffer = [0u8; 14]; + let mut writer = ManifestEntryMut::new(&mut buffer); + + writer.kind().try_write(Kind::SPROCKET).unwrap(); + writer.count().try_write(4).unwrap(); + writer.wide_kind().try_write(Kind::GEEGAW).unwrap(); + + let k_manifest_entry: [u8; 14] = [ + 0x01, // 0:1 Kind kind == SPROCKET + 0x04, 0x00, 0x00, 0x00, // 1:5 UInt count == 4 + 0x02, 0x00, 0x00, 0x00, // 5:9 Kind wide_kind == GEEGAW + 0x00, 0x00, 0x00, 0x00, 0x00, // wide_kind_in_bits unwritten + ]; + assert_eq!(&buffer[..], &k_manifest_entry[..]); +} diff --git a/compiler/back_end/experimental/rust/testcode/imported_test.rs b/compiler/back_end/experimental/rust/testcode/imported_test.rs new file mode 100644 index 00000000..7c5edfbb --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/imported_test.rs @@ -0,0 +1,38 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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. + +use emboss_runtime::{prelude::*, Error}; +use testdata_imported_emb::*; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_imported_inner_field_read() { + let values: [u8; 8] = [42, 0, 0, 0, 0, 0, 0, 0]; + + let view = Inner::new(&values[..]); + let _value_type_check: Result = view.value().try_read(); + + assert_eq!(view.value().try_read().unwrap(), 42); + } + + #[test] + fn test_out_of_bounds() { + let values: [u8; 1] = [42]; // Too short + let view = Inner::new(&values[..]); + assert_eq!(view.value().try_read(), Err(Error::OutOfBounds)); + } +} diff --git a/compiler/back_end/experimental/rust/testcode/importer_test.rs b/compiler/back_end/experimental/rust/testcode/importer_test.rs new file mode 100644 index 00000000..070c63cf --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/importer_test.rs @@ -0,0 +1,29 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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. + +use testdata_importer_emb::{Outer, OuterMut}; + +#[test] +fn test_importer() { + let mut buf = [0u8; 16]; + let mut importer = OuterMut::new(&mut buf[..]); + + // We should be able to access the nested imported inner struct. + let mut inner = importer.inner(); + let mut value = inner.value(); + value.try_write(1234).unwrap(); + + let importer_ro = Outer::new(&buf[..]); + assert_eq!(importer_ro.inner().value().try_read().unwrap(), 1234); +} diff --git a/compiler/back_end/experimental/rust/testcode/int_sizes_test.rs b/compiler/back_end/experimental/rust/testcode/int_sizes_test.rs new file mode 100644 index 00000000..5d633ee1 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/int_sizes_test.rs @@ -0,0 +1,60 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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. + +#[test] +fn reads_int_sizes_correctly() { + let container: &[u8] = &[ + 0x02, // 0:1 one_byte == 2 + 0xfc, 0xfe, // 1:3 two_byte == -260 + 0x66, 0x55, 0x44, // 3:6 three_byte == 0x445566 + 0xfa, 0xfa, 0xfb, 0xfc, // 6:10 four_byte == -0x03040506 + 0x21, 0x43, 0x65, 0x87, // 10:14 five_byte + 0x29, // 14:15 five_byte == 0x2987654321 + 0x44, 0x65, 0x87, 0xa9, // 15:19 six_byte + 0xcb, 0xed, // 19:21 six_byte == -0x123456789abc + 0x97, 0xa6, 0xb5, 0xc4, // 21:25 seven_byte + 0xd3, 0xe2, 0x71, // 25:28 seven_byte == 0x71e2d3c4b5a697 + 0xfa, 0xfa, 0xfb, 0xfc, // 28:32 eight_byte + 0xfd, 0xfe, 0xff, 0x80, // 32:36 eight_byte == -0x7f00010203040506 + ]; + + let view = testdata_int_sizes_emb::Sizes::new(container); + + assert_eq!(view.one_byte().try_read().unwrap(), 2i8); + assert_eq!(view.two_byte().try_read().unwrap(), -260i16); + assert_eq!(view.three_byte().try_read().unwrap(), 0x445566i32); + assert_eq!(view.four_byte().try_read().unwrap(), -0x03040506i32); + assert_eq!(view.five_byte().try_read().unwrap(), 0x2987654321i64); + assert_eq!(view.six_byte().try_read().unwrap(), -0x123456789abci64); + assert_eq!(view.seven_byte().try_read().unwrap(), 0x71e2d3c4b5a697i64); + assert_eq!( + view.eight_byte().try_read().unwrap(), + -0x7f00010203040506i64 + ); +} + +#[test] +fn reads_negative_ones_correctly() { + let container: &[u8] = &[0xff; 36]; + let view = testdata_int_sizes_emb::Sizes::new(container); + + assert_eq!(view.one_byte().try_read().unwrap(), -1i8); + assert_eq!(view.two_byte().try_read().unwrap(), -1i16); + assert_eq!(view.three_byte().try_read().unwrap(), -1i32); + assert_eq!(view.four_byte().try_read().unwrap(), -1i32); + assert_eq!(view.five_byte().try_read().unwrap(), -1i64); + assert_eq!(view.six_byte().try_read().unwrap(), -1i64); + assert_eq!(view.seven_byte().try_read().unwrap(), -1i64); + assert_eq!(view.eight_byte().try_read().unwrap(), -1i64); +} diff --git a/compiler/back_end/experimental/rust/testcode/nested_structure_test.rs b/compiler/back_end/experimental/rust/testcode/nested_structure_test.rs new file mode 100644 index 00000000..eebe25b7 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/nested_structure_test.rs @@ -0,0 +1,66 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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. + +use emboss_runtime::{prelude::*, Error}; + +#[test] +fn container_field_values_are_correct() { + let container: &[u8] = &[ + 0x28, 0x00, 0x00, 0x00, // 0:4 weight == 40 + 0x78, 0x56, 0x34, 0x12, // 4:8 important_box.id == 0x12345678 + 0x03, 0x02, 0x01, 0x00, // 8:12 important_box.count == 0x010203 + 0x21, 0x43, 0x65, 0x87, // 12:16 other_box.id == 0x87654321 + 0xcc, 0xbb, 0xaa, 0x00, // 16:20 other_box.count == 0xaabbcc + ]; + + let view = testdata_nested_structure_emb::Container::new(container); + + // Static explicit typing assertions + let weight: u32 = view.weight().try_read().unwrap(); + assert_eq!(weight, 40); + + let important_box = view.important_box(); + let important_box_id: u32 = important_box.id().try_read().unwrap(); + assert_eq!(important_box_id, 0x12345678); + let important_box_count: u32 = important_box.count().try_read().unwrap(); + assert_eq!(important_box_count, 0x010203); + + let other_box = view.other_box(); + let other_box_id: u32 = other_box.id().try_read().unwrap(); + assert_eq!(other_box_id, 0x87654321); + let other_box_count: u32 = other_box.count().try_read().unwrap(); + assert_eq!(other_box_count, 0xaabbcc); +} + +#[test] +fn nested_out_of_bounds_handles_cascading_reads_dynamically() { + let container: &[u8] = &[ + 0x28, 0x00, 0x00, 0x00, // 0:4 weight == 40 + 0x78, 0x56, 0x34, 0x12, // 4:8 important_box.id == 0x12345678 + 0x03, 0x02, 0x01, 0x00, // 8:12 important_box.count == 0x010203 + ]; // TRUNCATED struct! Missing the entire 8 bytes of other_box! + + let view = testdata_nested_structure_emb::Container::new(container); + + // weight and important_box can still be read successfully cleanly over the bounds organically + assert_eq!(view.weight().try_read().unwrap(), 40); + let important_box = view.important_box(); + assert_eq!(important_box.id().try_read().unwrap(), 0x12345678); + assert_eq!(important_box.count().try_read().unwrap(), 0x010203); + + // structurally other_box cleanly passes the Slice but natively aborts safely during Read because of OutOfBounds bounds over exactly its byte size safely propagating Error organically natively + let other_box = view.other_box(); + assert_eq!(other_box.id().try_read(), Err(Error::OutOfBounds)); + assert_eq!(other_box.count().try_read(), Err(Error::OutOfBounds)); +} diff --git a/compiler/back_end/experimental/rust/testcode/next_keyword_test.rs b/compiler/back_end/experimental/rust/testcode/next_keyword_test.rs new file mode 100644 index 00000000..48064be2 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/next_keyword_test.rs @@ -0,0 +1,48 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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. + +use emboss_runtime::Error; +use testdata_next_keyword_emb::*; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_next_keyword_fields_are_correctly_located() { + let values: [u8; 11] = [1, 0, 0, 0, 2, 0, 3, 5, 6, 7, 4]; + + let view = NextKeyword::new(&values[..]); + + // Native reading + let val32: u32 = view.value32().try_read().unwrap(); + assert_eq!(val32, 1); + + let val16: u16 = view.value16().try_read().unwrap(); + assert_eq!(val16, 2); + + let val8: u8 = view.value8().try_read().unwrap(); + assert_eq!(val8, 3); + + let val8_off: u8 = view.value8_offset().try_read().unwrap(); + assert_eq!(val8_off, 4); + } + + #[test] + fn test_out_of_bounds() { + let values: [u8; 2] = [1, 0]; // Too short + let view = NextKeyword::new(&values[..]); + assert_eq!(view.value32().try_read(), Err(Error::OutOfBounds)); + } +} diff --git a/embossc b/embossc index a5ad323f..472de459 100755 --- a/embossc +++ b/embossc @@ -40,10 +40,9 @@ def _parse_args(argv): ) parser.add_argument( "--generate", - nargs=1, - choices=["cc"], + choices=["cc", "rust"], default="cc", - help="Which back end to use. Currently only C++ is supported.", + help="Which back end to use. Currently C++ ('cc') and Rust ('rust') are supported.", ) parser.add_argument( "--output-path", @@ -67,16 +66,61 @@ def _parse_args(argv): return parser.parse_args(argv[1:]) +def _generate_code_and_log_errors(ir, flags): + match flags.generate: + case "rust": + from compiler.back_end.experimental.rust import ( + emboss_codegen_rust, + ) # pylint:disable=import-outside-toplevel + + return emboss_codegen_rust.generate_code_and_log_errors( + ir, flags.color_output + ) + case "cc": + from compiler.back_end.cpp import ( + emboss_codegen_cpp, + header_generator, + ) # pylint:disable=import-outside-toplevel + + config = header_generator.Config(include_enum_traits=flags.cc_enum_traits) + return emboss_codegen_cpp.generate_headers_and_log_errors( + ir, flags.color_output, config + ) + case _: + raise ValueError(f"Unknown generation language: {flags.generate}") + + +def _default_file_suffix(generate_lang): + match generate_lang: + case "rust": + return ".rs" + case "cc": + return ".h" + case _: + raise ValueError(f"Unknown generation language: {generate_lang}") + + +def _format_code(code, generate_lang): + match generate_lang: + case "rust": + return code + case "cc": + from compiler.back_end.cpp import ( + emboss_codegen_cpp, + ) # pylint:disable=import-outside-toplevel + + return emboss_codegen_cpp.format_header(code) + case _: + raise ValueError(f"Unknown generation language: {generate_lang}") + + def main(argv): flags = _parse_args(argv) base_path = os.path.dirname(__file__) or "." sys.path.append(base_path) - from compiler.back_end.cpp import ( # pylint:disable=import-outside-toplevel - emboss_codegen_cpp, header_generator - ) - from compiler.front_end import ( # pylint:disable=import-outside-toplevel - emboss_front_end + from compiler.front_end import ( # pylint:disable=import-outside-toplevel + emboss_front_end, ) ir, _, errors = emboss_front_end.parse_and_log_errors( @@ -86,24 +130,20 @@ def main(argv): if errors: return 1 - config = header_generator.Config(include_enum_traits=flags.cc_enum_traits) - header, errors = emboss_codegen_cpp.generate_headers_and_log_errors( - ir, flags.color_output, config - ) - + code, errors = _generate_code_and_log_errors(ir, flags) if errors: return 1 if flags.output_file: output_file = flags.output_file[0] else: - output_file = flags.input_file[0] + ".h" + output_file = flags.input_file[0] + _default_file_suffix(flags.generate) output_filepath = os.path.join(flags.output_path[0], output_file) os.makedirs(os.path.dirname(output_filepath), exist_ok=True) with open(output_filepath, "w") as output: - output.write(emboss_codegen_cpp.format_header(header)) + output.write(_format_code(code, flags.generate)) return 0 diff --git a/runtime/experimental/rust/BUILD b/runtime/experimental/rust/BUILD new file mode 100644 index 00000000..6dbb3c9c --- /dev/null +++ b/runtime/experimental/rust/BUILD @@ -0,0 +1,28 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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. + +load("@rules_rust//rust:defs.bzl", "rust_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//visibility:public"], +) + +rust_library( + name = "emboss_runtime", + srcs = [ + "src/lib.rs", + "src/prelude.rs", + ], +) diff --git a/runtime/experimental/rust/Cargo.toml b/runtime/experimental/rust/Cargo.toml new file mode 100644 index 00000000..9c0f9ffb --- /dev/null +++ b/runtime/experimental/rust/Cargo.toml @@ -0,0 +1,20 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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. + +[package] +name = "emboss_runtime" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/runtime/experimental/rust/src/lib.rs b/runtime/experimental/rust/src/lib.rs new file mode 100644 index 00000000..d2933650 --- /dev/null +++ b/runtime/experimental/rust/src/lib.rs @@ -0,0 +1,568 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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. + +pub mod prelude; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + OutOfBounds, + UnknownEnum(T), +} + +impl Error { + pub fn map_type(self) -> Error { + match self { + Error::OutOfBounds => Error::OutOfBounds, + Error::UnknownEnum(_) => unreachable!("Cannot map UnknownEnum across unmodified types"), + } + } +} + +pub trait Storage { + type Sliced<'a>: Storage where Self: 'a; + fn slice(&self, offset: usize, length: usize) -> Result, Error>; + fn try_read_byte(&self, offset: usize) -> Result; +} + +pub trait MutStorage: Storage { + type SlicedMut<'a>: MutStorage where Self: 'a; + fn slice_mut(&mut self, offset: usize, length: usize) -> Result, Error>; + fn try_write_byte(&mut self, offset: usize, val: u8) -> Result<(), Error>; +} + +impl<'a, T: ?Sized + AsRef<[u8]>> Storage for &'a mut T { + type Sliced<'b> = Result<&'b [u8], Error> where Self: 'b; + fn slice(&self, offset: usize, length: usize) -> Result, Error> { + let bytes = self.as_ref(); + Ok(bytes.get(offset..offset + length).ok_or(Error::OutOfBounds)) + } + fn try_read_byte(&self, offset: usize) -> Result { + let bytes = self.as_ref(); + bytes.get(offset).copied().ok_or(Error::OutOfBounds) + } +} + +impl<'a, T: ?Sized + AsRef<[u8]>> Storage for &'a T { + type Sliced<'b> = Result<&'b [u8], Error> where Self: 'b; + fn slice(&self, offset: usize, length: usize) -> Result, Error> { + let bytes = (*self).as_ref(); + Ok(bytes.get(offset..offset + length).ok_or(Error::OutOfBounds)) + } + fn try_read_byte(&self, offset: usize) -> Result { + let bytes = self.as_ref(); + bytes.get(offset).copied().ok_or(Error::OutOfBounds) + } +} + +impl<'a, T: ?Sized + AsMut<[u8]> + AsRef<[u8]>> MutStorage for &'a mut T { + type SlicedMut<'b> = Result<&'b mut [u8], Error> where Self: 'b; + fn slice_mut(&mut self, offset: usize, length: usize) -> Result, Error> { + let bytes = self.as_mut(); + Ok(bytes.get_mut(offset..offset + length).ok_or(Error::OutOfBounds)) + } + fn try_write_byte(&mut self, offset: usize, val: u8) -> Result<(), Error> { + let bytes = self.as_mut(); + let b = bytes.get_mut(offset).ok_or(Error::OutOfBounds)?; + *b = val; + Ok(()) + } +} + +impl Storage for Result { + type Sliced<'a> = Result, Error> where Self: 'a; + fn slice(&self, offset: usize, length: usize) -> Result, Error> { + match self { + Ok(s) => match s.slice(offset, length) { + Ok(sliced) => Ok(Ok(sliced)), + Err(e) => Err(e), + }, + Err(e) => Err(*e), + } + } + fn try_read_byte(&self, offset: usize) -> Result { + match self { + Ok(s) => s.try_read_byte(offset), + Err(e) => Err(*e), + } + } +} + +impl MutStorage for Result { + type SlicedMut<'a> = Result, Error> where Self: 'a; + fn slice_mut(&mut self, offset: usize, length: usize) -> Result, Error> { + match self { + Ok(s) => match s.slice_mut(offset, length) { + Ok(sliced) => Ok(Ok(sliced)), + Err(e) => Err(e), + }, + Err(e) => Err(*e), + } + } + fn try_write_byte(&mut self, offset: usize, val: u8) -> Result<(), Error> { + match self { + Ok(s) => s.try_write_byte(offset, val), + Err(e) => Err(*e), + } + } +} + +pub trait ByteOrder { + fn shift(i: usize, size_in_bytes: usize) -> usize; +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct LittleEndian; +impl ByteOrder for LittleEndian { + #[inline(always)] + fn shift(i: usize, _size_in_bytes: usize) -> usize { + i * 8 + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct BigEndian; +impl ByteOrder for BigEndian { + #[inline(always)] + fn shift(i: usize, size_in_bytes: usize) -> usize { + (size_in_bytes - 1 - i) * 8 + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Null; +impl ByteOrder for Null { + #[inline(always)] + fn shift(_i: usize, _size_in_bytes: usize) -> usize { + 0 + } +} + +pub trait DecodeFromStorage: Sized { + fn decode(storage: &S, size_in_bytes: usize) -> Result; +} + +pub trait EncodeToStorage: Sized { + fn encode(&self, storage: &mut S, size_in_bytes: usize) -> Result<(), Error>; +} + +macro_rules! impl_decode_uint { + ($type:ty) => { + impl DecodeFromStorage for $type { + fn decode(storage: &S, size_in_bytes: usize) -> Result { + let mut val: $type = 0; + for i in 0..size_in_bytes { + val |= (storage.try_read_byte(i)? as $type) << E::shift(i, size_in_bytes); + } + Ok(val) + } + } + impl EncodeToStorage for $type { + fn encode(&self, storage: &mut S, size_in_bytes: usize) -> Result<(), Error> { + for i in 0..size_in_bytes { + storage.try_write_byte(i, ((*self) >> E::shift(i, size_in_bytes)) as u8)?; + } + Ok(()) + } + } + }; +} +impl_decode_uint!(u8); +impl_decode_uint!(u16); +impl_decode_uint!(u32); +impl_decode_uint!(u64); + +pub trait SmallestUInt { + type T; + fn from_u64(val: u64) -> Self::T; +} +pub struct SizeSelector; + +macro_rules! impl_smallest_uint { + ($type:ty, $($bits:expr),+) => { + $( + impl SmallestUInt for SizeSelector<$bits> { + type T = $type; + #[inline] + fn from_u64(val: u64) -> Self::T { val as $type } + } + )+ + }; +} +impl_smallest_uint!(u8, 1, 2, 3, 4, 5, 6, 7, 8); +impl_smallest_uint!(u16, 9, 10, 11, 12, 13, 14, 15, 16); +impl_smallest_uint!( + u32, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 +); +impl_smallest_uint!( + u64, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48 +); +impl_smallest_uint!( + u64, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 +); + +pub struct UInt { + storage: S, + _marker: core::marker::PhantomData, +} + +impl UInt { + pub fn new(storage: S) -> Self { + Self { + storage, + _marker: core::marker::PhantomData, + } + } +} + +impl UInt +where + SizeSelector: SmallestUInt, + as SmallestUInt>::T: DecodeFromStorage, +{ + pub fn try_read(&self) -> Result< as SmallestUInt>::T, Error> { + let size_in_bytes = (BITS + 7) / 8; + < as SmallestUInt>::T as DecodeFromStorage>::decode( + &self.storage, + size_in_bytes, + ) + } +} + +impl UInt +where + SizeSelector: SmallestUInt, + as SmallestUInt>::T: EncodeToStorage, +{ + pub fn try_write(&mut self, val: as SmallestUInt>::T) -> Result<(), Error> { + let size_in_bytes = (BITS + 7) / 8; + val.encode(&mut self.storage, size_in_bytes) + } +} + +pub trait SmallestInt { + type T; + type U; + fn sign_extend(raw: Self::U, bits: usize) -> Self::T; + fn mask_to_unsigned(val: Self::T, bits: usize) -> Self::U; +} + +macro_rules! impl_smallest_int { + ($type:ty, $utype:ty, $($bits:expr),+) => { + $( + impl SmallestInt for SizeSelector<$bits> { + type T = $type; + type U = $utype; + #[inline] + fn sign_extend(raw: Self::U, bits: usize) -> Self::T { + let shift_amount = (core::mem::size_of::<$type>() * 8) - bits; + let sign_extended = (raw as $type) << shift_amount; + sign_extended >> shift_amount + } + #[inline] + fn mask_to_unsigned(val: Self::T, bits: usize) -> Self::U { + let mask = if bits == core::mem::size_of::<$utype>() * 8 { + !0 + } else { + (1 << bits) - 1 + }; + (val as Self::U) & mask + } + } + )+ + }; +} + +impl_smallest_int!(i8, u8, 1, 2, 3, 4, 5, 6, 7, 8); +impl_smallest_int!(i16, u16, 9, 10, 11, 12, 13, 14, 15, 16); +impl_smallest_int!( + i32, u32, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 +); +impl_smallest_int!( + i64, u64, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48 +); +impl_smallest_int!( + i64, u64, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 +); + +pub struct Int { + storage: S, + _marker: core::marker::PhantomData, +} + +impl Int { + pub fn new(storage: S) -> Self { + Self { + storage, + _marker: core::marker::PhantomData, + } + } +} + +impl Int +where + SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, + as SmallestUInt>::T: DecodeFromStorage, +{ + pub fn try_read(&self) -> Result< as SmallestInt>::T, Error> { + let size_in_bytes = (BITS + 7) / 8; + let slice = self.storage.slice(0, size_in_bytes)?; + let raw = UInt::>::new(slice).try_read()?; + Ok( as SmallestInt>::sign_extend(raw, BITS)) + } +} + +impl Int +where + SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, + as SmallestUInt>::T: EncodeToStorage, +{ + pub fn try_write(&mut self, val: as SmallestInt>::T) -> Result<(), Error> { + let unsigned = as SmallestInt>::mask_to_unsigned(val, BITS); + let mut uint_view = UInt::>::new(self.storage.slice_mut(0, (BITS + 7) / 8)?); + uint_view.try_write(unsigned) + } +} + +pub trait TryFromRaw { + fn try_from_raw(val: T) -> Result> + where + Self: Sized; +} + +pub struct EnumView { + pub inner: Inner, + _phantom: core::marker::PhantomData, +} + +impl EnumView { + pub fn new(inner: Inner) -> Self { + Self { + inner, + _phantom: core::marker::PhantomData, + } + } +} + +pub struct EnumViewMut { + pub inner: Inner, + _phantom: core::marker::PhantomData, +} + +impl EnumViewMut { + pub fn new(inner: Inner) -> Self { + Self { + inner, + _phantom: core::marker::PhantomData, + } + } +} + +pub trait TryRead { + type ReadValue; + fn try_read(&self) -> Result; +} + +pub trait TryWrite { + type WriteValue; + fn try_write(&mut self, val: Self::WriteValue) -> Result<(), Error>; +} + +impl TryRead for UInt +where + SizeSelector: SmallestUInt, + as SmallestUInt>::T: DecodeFromStorage, +{ + type ReadValue = as SmallestUInt>::T; + fn try_read(&self) -> Result { + self.try_read() + } +} + +impl TryWrite for UInt +where + SizeSelector: SmallestUInt, + as SmallestUInt>::T: EncodeToStorage, +{ + type WriteValue = as SmallestUInt>::T; + fn try_write(&mut self, val: Self::WriteValue) -> Result<(), Error> { + self.try_write(val) + } +} + +impl TryRead for Int +where + SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, + as SmallestUInt>::T: DecodeFromStorage, +{ + type ReadValue = as SmallestInt>::T; + fn try_read(&self) -> Result { + self.try_read() + } +} + +impl TryWrite for Int +where + SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, + as SmallestUInt>::T: EncodeToStorage, +{ + type WriteValue = as SmallestInt>::T; + fn try_write(&mut self, val: Self::WriteValue) -> Result<(), Error> { + self.try_write(val) + } +} + +impl EnumView +where + Inner: TryRead, + T: TryFromRaw, +{ + pub fn try_read(&self) -> Result> { + let raw = self.inner.try_read().map_err(|e| e.map_type())?; + T::try_from_raw(raw) + } +} + +impl EnumViewMut +where + Inner: TryWrite, + Inner::WriteValue: From, +{ + pub fn try_write(&mut self, val: T) -> Result<(), Error> { + self.inner.try_write(Inner::WriteValue::from(val)) + } +} + +impl EnumViewMut +where + Inner: TryRead, + T: TryFromRaw, +{ + pub fn try_read(&self) -> Result> { + let raw = self.inner.try_read().map_err(|e| e.map_type())?; + T::try_from_raw(raw) + } +} + +pub struct BitUInt { + storage: S, + _marker: core::marker::PhantomData, +} + +impl BitUInt { + pub fn new(storage: S) -> Self { + Self { + storage, + _marker: core::marker::PhantomData, + } + } +} + +impl BitUInt +where + SizeSelector: SmallestUInt, +{ + pub fn try_read(&self) -> Result< as SmallestUInt>::T, Error> { + let mut val: u64 = 0; + let first_byte = BIT_OFFSET / 8; + let last_byte = (BIT_OFFSET + BITS - 1) / 8; + + for i in first_byte..=last_byte { + let byte_val = self.storage.try_read_byte(i)? as u64; + let bits_from_byte_offset = i * 8; + + if bits_from_byte_offset >= BIT_OFFSET { + let shift = bits_from_byte_offset - BIT_OFFSET; + if shift < 64 { + val |= byte_val << shift; + } + } else { + let shift = BIT_OFFSET - bits_from_byte_offset; + val |= byte_val >> shift; + } + } + + let mask = if BITS == 64 { !0 } else { (1 << BITS) - 1 }; + Ok( as SmallestUInt>::from_u64(val & mask)) + } +} + +pub struct BitInt { + storage: S, + _marker: core::marker::PhantomData, +} + +impl BitInt { + pub fn new(storage: S) -> Self { + Self { + storage, + _marker: core::marker::PhantomData, + } + } +} + +impl BitInt +where + SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, +{ + pub fn try_read(&self) -> Result< as SmallestInt>::T, Error> { + let uint_view = BitUInt::>::new(self.storage.slice(0, (BIT_OFFSET + BITS + 7) / 8)?); + let raw = uint_view.try_read()?; + Ok( as SmallestInt>::sign_extend(raw, BITS)) + } +} + +impl TryRead for BitUInt +where + SizeSelector: SmallestUInt, +{ + type ReadValue = as SmallestUInt>::T; + fn try_read(&self) -> Result { + self.try_read() + } +} + +impl TryRead for BitInt +where + SizeSelector: SmallestUInt + SmallestInt as SmallestUInt>::T>, +{ + type ReadValue = as SmallestInt>::T; + fn try_read(&self) -> Result { + self.try_read() + } +} + +#[derive(Clone, Copy, Debug)] +pub struct VirtualField { + pub value: core::result::Result, +} + +impl VirtualField { + pub const fn new(value: core::result::Result) -> Self { + Self { value } + } +} + +impl VirtualField { + pub fn try_read(&self) -> core::result::Result { + self.value + } +} + +impl TryRead for VirtualField { + type ReadValue = T; + fn try_read(&self) -> core::result::Result { + self.value + } +} + diff --git a/runtime/experimental/rust/src/prelude.rs b/runtime/experimental/rust/src/prelude.rs new file mode 100644 index 00000000..1d4c5f91 --- /dev/null +++ b/runtime/experimental/rust/src/prelude.rs @@ -0,0 +1,15 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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. + +pub use crate::{Int, UInt}; diff --git a/testdata/BUILD b/testdata/BUILD index 2d3f6231..5bd4b1e1 100644 --- a/testdata/BUILD +++ b/testdata/BUILD @@ -51,6 +51,7 @@ filegroup( "condition.emb", "cpp_namespace.emb", "dynamic_size.emb", + "endian.emb", "enum.emb", "enum_case.emb", "explicit_sizes.emb", @@ -107,6 +108,13 @@ emboss_cc_library( ], ) +emboss_cc_library( + name = "endian_emboss", + srcs = [ + "endian.emb", + ], +) + emboss_cc_library( name = "enum_emboss", srcs = [ @@ -374,3 +382,61 @@ emboss_cc_library( "many_conditionals.emb", ], ) + + +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") +load("//:build_defs.bzl", "emboss_rust_library") + +emboss_rust_library( + name = "imported_emb_rs", + srcs = ["imported.emb"], +) + +emboss_rust_library( + name = "imported_genfiles_emb_rs", + srcs = ["imported_genfiles.emb"], +) + +emboss_rust_library( + name = "importer_emb_rs", + srcs = ["importer.emb"], + deps = [ + ":imported_emb_rs", + ":imported_genfiles_emb_rs", + ], +) + +emboss_rust_library( + name = "next_keyword_emb_rs", + srcs = ["next_keyword.emb"], +) + +emboss_rust_library( + name = "nested_structure_emb_rs", + srcs = ["nested_structure.emb"], +) + +emboss_rust_library( + name = "endian_emb_rs", + srcs = ["endian.emb"], +) + +emboss_rust_library( + name = "int_sizes_emb_rs", + srcs = ["int_sizes.emb"], +) + +emboss_rust_library( + name = "enum_emb_rs", + srcs = ["enum.emb"], +) + +emboss_rust_library( + name = "dynamic_size_emb_rs", + srcs = ["dynamic_size.emb"], +) + +emboss_rust_library( + name = "auto_array_size_emb_rs", + srcs = ["auto_array_size.emb"], +) diff --git a/testdata/endian.emb b/testdata/endian.emb new file mode 100644 index 00000000..7e2b9edd --- /dev/null +++ b/testdata/endian.emb @@ -0,0 +1,21 @@ +# Copyright 2026 Google LLC +# +# 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 +# +# https://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. + +[$default byte_order: "LittleEndian"] +[(cpp) namespace: "emboss::test"] + +struct Message: + 0 [+4] UInt little_uint32 + 4 [+4] UInt big_uint32 [byte_order: "BigEndian"] + 8 [+1] UInt single_byte [byte_order: "Null"]