From 4114ab8d693939731290e7047fff745ad2ccc206 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 01/19] rust: Hook up experimental generator --- compiler/back_end/experimental/__init__.py | 13 ++++ .../back_end/experimental/rust/__init__.py | 15 ++++ .../experimental/rust/emboss_codegen_rust.py | 49 +++++++++++++ embossc | 70 +++++++++++++++---- 4 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 compiler/back_end/experimental/__init__.py create mode 100644 compiler/back_end/experimental/rust/__init__.py create mode 100644 compiler/back_end/experimental/rust/emboss_codegen_rust.py 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/__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..4adb56fa --- /dev/null +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -0,0 +1,49 @@ +# 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.util import error +from compiler.util import ir_data + +ColorOutput = Literal["always", "never", "if_tty", "auto"] +Source = str +ErrorList = list[list[error._Message]] + + +def _show_errors( + errors: ErrorList, ir: ir_data.EmbossIr, color_output: ColorOutput +) -> None: + """Prints errors with source code snippets.""" + source_codes = {} + 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 generate_code_and_log_errors( + ir: ir_data.EmbossIr, color_output: ColorOutput +) -> tuple[Source, ErrorList]: + """Generates Rust source code and definitions for the provided Emboss IR.""" + errors = [[error.error(None, None, "Rust backend is not yet supported")]] + if errors: + _show_errors(errors, ir, color_output) + return "", errors 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 From dc9ffe5ce59c951db7d46cf21ce1bec228a63cc9 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 02/19] rust: Establish base backend (little-endian UInt only) --- MODULE.bazel | 10 + build_defs.bzl | 65 +++ compiler/back_end/experimental/rust/BUILD | 23 ++ .../experimental/rust/emboss_codegen_rust.py | 390 +++++++++++++++++- .../rust/generated_code_templates | 39 ++ .../back_end/experimental/rust/testcode/BUILD | 30 ++ .../rust/testcode/imported_test.rs | 24 ++ .../rust/testcode/next_keyword_test.rs | 34 ++ runtime/experimental/rust/BUILD | 14 + runtime/experimental/rust/Cargo.toml | 6 + runtime/experimental/rust/src/lib.rs | 120 ++++++ runtime/experimental/rust/src/prelude.rs | 15 + 12 files changed, 764 insertions(+), 6 deletions(-) create mode 100644 compiler/back_end/experimental/rust/BUILD create mode 100644 compiler/back_end/experimental/rust/generated_code_templates create mode 100644 compiler/back_end/experimental/rust/testcode/BUILD create mode 100644 compiler/back_end/experimental/rust/testcode/imported_test.rs create mode 100644 compiler/back_end/experimental/rust/testcode/next_keyword_test.rs create mode 100644 runtime/experimental/rust/BUILD create mode 100644 runtime/experimental/rust/Cargo.toml create mode 100644 runtime/experimental/rust/src/lib.rs create mode 100644 runtime/experimental/rust/src/prelude.rs diff --git a/MODULE.bazel b/MODULE.bazel index f8c68525..1b0e505c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -18,6 +18,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..46922bc0 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,67 @@ 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 emboss_rust_library(name, deps, rust_deps = [], **kwargs): + """Constructs a Rust library from an emboss_library target. + + Args: + name: The name of the resulting rust_library. + deps: A list containing exactly one emboss_library dependency. + rust_deps: Dependencies to forward to the rust_library. + **kwargs: Additional arguments to pass to rust_library. + """ + codegen_name = name + "_srcs" + + _rust_emboss_codegen( + name = codegen_name, + deps = deps, + ) + + rust_library( + name = name, + srcs = [":" + codegen_name], + deps = rust_deps + ["//runtime/experimental/rust:emboss_runtime"], + **kwargs + ) diff --git a/compiler/back_end/experimental/rust/BUILD b/compiler/back_end/experimental/rust/BUILD new file mode 100644 index 00000000..fbbf14b3 --- /dev/null +++ b/compiler/back_end/experimental/rust/BUILD @@ -0,0 +1,23 @@ +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/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index 4adb56fa..70b447ba 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -18,32 +18,410 @@ 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 = {"Int", "Bcd", "Flag", "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 = {} - for module in ir.module: - source_codes[module.source_file_name] = module.source_text + 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.", + ) + ] + ) + + struct_definitions = [] + for module in ir.module: + + real_imports = [ + imp for imp in module.foreign_import if imp.file_name.text != "" + ] + if real_imports: + diagnostics.append( + [ + error.warn( + module.source_file_name, + real_imports[0].source_location, + f"Imports are not yet supported in this backend. Foreign imports will be omitted.", + ) + ] + ) + + 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, + struct_definitions="".join(struct_definitions), + ) + + return rust_source, diagnostics + + +def _generate_type(type_ir, ir, module, templates, diagnostics) -> str: + definitions = [] + + 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_ir.name.name.text}' 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: + diagnostics.append( + [ + error.warn( + module.source_file_name, + type_ir.subtype[0].source_location, + f"Subtypes are not yet supported in this backend. All subtypes in '{type_ir.name.name.text}' will be omitted.", + ) + ] + ) + + if type_ir.addressable_unit != 8: + diagnostics.append( + [ + error.warn( + module.source_file_name, + type_ir.source_location, + f"Bit-level structs are not yet supported in this backend. Type '{type_ir.name.name.text}' will be omitted.", + ) + ] + ) + return "" + + if not type_ir.has_field("structure"): + diagnostics.append( + [ + error.warn( + module.source_file_name, + type_ir.source_location, + f"Non-structure types are not yet supported in this backend. Type '{type_ir.name.name.text}' will be omitted.", + ) + ] + ) + return "".join(definitions) + + definitions.append(_generate_struct(type_ir, ir, module, templates, diagnostics)) + return "".join(definitions) + + +def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: + struct_name = type_ir.name.name.text + field_accessors = [] + + for field in type_ir.structure.field: + 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"): + 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("type") or not field.type.has_field("atomic_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 types are not yet supported in this backend. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + + source_name = field.type.atomic_type.reference.source_name[0].text + + if source_name in _UNSUPPORTED_PRELUDE_TYPES: + diagnostics.append( + [ + error.warn( + module.source_file_name, + field.source_location, + f"Type '{source_name}' is not yet supported in this backend. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + + if not field.has_field("location"): + diagnostics.append( + [ + error.warn( + module.source_file_name, + field.source_location, + f"Virtual fields are not yet supported in this backend. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + + if not ir_util.is_constant(field.location.start) or not ir_util.is_constant( + field.location.size + ): + diagnostics.append( + [ + error.warn( + module.source_file_name, + field.source_location, + f"Non-constant size or offset is not yet supported in this backend. 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. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + + byte_order_attr = ir_util.get_attribute(field.attribute, "byte_order") + if byte_order_attr: + byte_order = byte_order_attr.string_constant.text + if byte_order != "LittleEndian": + diagnostics.append( + [ + error.warn( + module.source_file_name, + byte_order_attr.source_location, + f"Byte order '{byte_order}' is not yet supported in this backend. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + + byte_offset = ir_util.constant_value(field.location.start) + byte_length = ir_util.constant_value(field.location.size) + + referenced_type = ir_util.find_object(field.type.atomic_type.reference, ir) + + if referenced_type not in module.type and not referenced_type.has_field( + "external" + ): + diagnostics.append( + [ + error.warn( + module.source_file_name, + field.source_location, + f"Cross-module dependencies are not yet supported in this backend. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + + if referenced_type.has_field("external"): + bits = byte_length * 8 + if field.type.has_field("size_in_bits"): + bits = ir_util.constant_value(field.type.size_in_bits) + + field_accessors.append( + code_template.format_template( + templates.external_field_accessor, + field_name=field_name, + type_name=source_name, + bits=str(bits), + 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 + + return code_template.format_template( + templates.struct_view, + struct_name=struct_name, + field_accessors="".join(field_accessors), + ) + + def generate_code_and_log_errors( ir: ir_data.EmbossIr, color_output: ColorOutput ) -> tuple[Source, ErrorList]: - """Generates Rust source code and definitions for the provided Emboss IR.""" - errors = [[error.error(None, None, "Rust backend is not yet supported")]] + """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: - _show_errors(errors, ir, color_output) - return "", 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..d9644a56 --- /dev/null +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -0,0 +1,39 @@ +// 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)] + +use emboss_runtime::prelude::*; +use emboss_runtime::{Error, Storage}; + +${struct_definitions} + +// ** struct_view ** // +pub struct ${struct_name} { + storage: S, +} + +impl ${struct_name} { + pub fn new(storage: S) -> Self { + Self { storage } + } +${field_accessors} +} + +// ** external_field_accessor ** // + pub fn ${field_name}(&self) -> ${type_name}<${bits}, S::Sliced> { + ${type_name}::<${bits}, S::Sliced>::new(self.storage.slice(${byte_offset}, ${byte_length})) + } diff --git a/compiler/back_end/experimental/rust/testcode/BUILD b/compiler/back_end/experimental/rust/testcode/BUILD new file mode 100644 index 00000000..412ac779 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/BUILD @@ -0,0 +1,30 @@ +load("@rules_rust//rust:defs.bzl", "rust_test") +load("//:build_defs.bzl", "emboss_rust_library") + +emboss_rust_library( + name = "imported_emb_rs", + deps = ["//testdata:imported_emboss_ir"], +) + +emboss_rust_library( + name = "next_keyword_emb_rs", + deps = ["//testdata:next_keyword_emboss_ir"], +) + +rust_test( + name = "imported_test", + srcs = ["imported_test.rs"], + deps = [ + ":imported_emb_rs", + "//runtime/experimental/rust:emboss_runtime", + ], +) + +rust_test( + name = "next_keyword_test", + srcs = ["next_keyword_test.rs"], + deps = [ + ":next_keyword_emb_rs", + "//runtime/experimental/rust:emboss_runtime", + ], +) 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..0daae3a4 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/imported_test.rs @@ -0,0 +1,24 @@ +use emboss_runtime::{prelude::*, Error}; +use imported_emb_rs::*; + +#[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/next_keyword_test.rs b/compiler/back_end/experimental/rust/testcode/next_keyword_test.rs new file mode 100644 index 00000000..d985e2b9 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/next_keyword_test.rs @@ -0,0 +1,34 @@ +use emboss_runtime::Error; +use next_keyword_emb_rs::*; + +#[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/runtime/experimental/rust/BUILD b/runtime/experimental/rust/BUILD new file mode 100644 index 00000000..cfa2c36e --- /dev/null +++ b/runtime/experimental/rust/BUILD @@ -0,0 +1,14 @@ +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..ba72c636 --- /dev/null +++ b/runtime/experimental/rust/Cargo.toml @@ -0,0 +1,6 @@ +[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..315979eb --- /dev/null +++ b/runtime/experimental/rust/src/lib.rs @@ -0,0 +1,120 @@ +// 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, +} + +pub trait Storage: Clone { + type Sliced: Storage; + fn slice(&self, offset: usize, length: usize) -> Self::Sliced; + fn try_read_byte(&self, offset: usize) -> Result; +} + +impl<'a, T: ?Sized + AsRef<[u8]>> Storage for &'a T { + type Sliced = Result<&'a [u8], Error>; + fn slice(&self, offset: usize, length: usize) -> Self::Sliced { + let bytes = (*self).as_ref(); + 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 Storage for Result { + type Sliced = Result; + fn slice(&self, offset: usize, length: usize) -> Self::Sliced { + match self { + Ok(s) => Ok(s.slice(offset, length)), + 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), + } + } +} + +pub trait DecodeFromStorage: Sized { + fn decode(storage: &S, size_in_bytes: usize) -> Result; +} + +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) << (i * 8); + } + Ok(val) + } + } + }; +} +impl_decode_uint!(u8); +impl_decode_uint!(u16); +impl_decode_uint!(u32); +impl_decode_uint!(u64); + +pub trait SmallestUInt { + type T: DecodeFromStorage; +} +pub struct SizeSelector; + +macro_rules! impl_smallest_uint { + ($type:ty, $($bits:expr),+) => { + $( + impl SmallestUInt for SizeSelector<$bits> { + type T = $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, +} + +impl UInt +where + SizeSelector: SmallestUInt, +{ + pub fn new(storage: S) -> Self { + Self { storage } + } + + pub fn try_read(&self) -> Result< as SmallestUInt>::T, Error> { + let size_in_bytes = (BITS + 7) / 8; + < as SmallestUInt>::T>::decode(&self.storage, size_in_bytes) + } +} diff --git a/runtime/experimental/rust/src/prelude.rs b/runtime/experimental/rust/src/prelude.rs new file mode 100644 index 00000000..0177e32e --- /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::UInt; From 12987d4ac23597991063417c81af9048215d3d5e Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 03/19] rust: Add nested struct support --- .../experimental/rust/emboss_codegen_rust.py | 10 ++++ .../rust/generated_code_templates | 5 ++ .../back_end/experimental/rust/testcode/BUILD | 14 +++++ .../rust/testcode/nested_structure_test.rs | 52 +++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 compiler/back_end/experimental/rust/testcode/nested_structure_test.rs diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index 70b447ba..eab798dd 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -357,6 +357,16 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: 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.replace(".", "::"), + byte_offset=str(byte_offset), + byte_length=str(byte_length), + ) + ) else: diagnostics.append( [ diff --git a/compiler/back_end/experimental/rust/generated_code_templates b/compiler/back_end/experimental/rust/generated_code_templates index d9644a56..2f77dcc8 100644 --- a/compiler/back_end/experimental/rust/generated_code_templates +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -37,3 +37,8 @@ ${field_accessors} pub fn ${field_name}(&self) -> ${type_name}<${bits}, S::Sliced> { ${type_name}::<${bits}, S::Sliced>::new(self.storage.slice(${byte_offset}, ${byte_length})) } + +// ** struct_field_accessor ** // + pub fn ${field_name}(&self) -> ${type_name} { + ${type_name}::new(self.storage.slice(${byte_offset}, ${byte_length})) + } diff --git a/compiler/back_end/experimental/rust/testcode/BUILD b/compiler/back_end/experimental/rust/testcode/BUILD index 412ac779..e447714f 100644 --- a/compiler/back_end/experimental/rust/testcode/BUILD +++ b/compiler/back_end/experimental/rust/testcode/BUILD @@ -28,3 +28,17 @@ rust_test( "//runtime/experimental/rust:emboss_runtime", ], ) + +emboss_rust_library( + name = "nested_structure_emb_rs", + deps = ["//testdata:nested_structure_emboss_ir"], +) + +rust_test( + name = "nested_structure_test", + srcs = ["nested_structure_test.rs"], + deps = [ + ":nested_structure_emb_rs", + "//runtime/experimental/rust:emboss_runtime", + ], +) 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..3b43d697 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/nested_structure_test.rs @@ -0,0 +1,52 @@ +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 = nested_structure_emb_rs::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 = nested_structure_emb_rs::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)); +} From d0346ddd1d1945ba4afca8fb3714d3e23e026321 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 04/19] rust: Add endian support --- .../experimental/rust/emboss_codegen_rust.py | 14 +---- .../rust/generated_code_templates | 4 +- .../back_end/experimental/rust/testcode/BUILD | 14 +++++ .../experimental/rust/testcode/endian_test.rs | 51 +++++++++++++++++ runtime/experimental/rust/src/lib.rs | 55 ++++++++++++++++--- testdata/BUILD | 8 +++ testdata/endian.emb | 21 +++++++ 7 files changed, 146 insertions(+), 21 deletions(-) create mode 100644 compiler/back_end/experimental/rust/testcode/endian_test.rs create mode 100644 testdata/endian.emb diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index eab798dd..c1638fa8 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -311,17 +311,8 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: byte_order_attr = ir_util.get_attribute(field.attribute, "byte_order") if byte_order_attr: byte_order = byte_order_attr.string_constant.text - if byte_order != "LittleEndian": - diagnostics.append( - [ - error.warn( - module.source_file_name, - byte_order_attr.source_location, - f"Byte order '{byte_order}' is not yet supported in this backend. Field '{field_name}' will be omitted.", - ) - ] - ) - continue + else: + byte_order = "Null" byte_offset = ir_util.constant_value(field.location.start) byte_length = ir_util.constant_value(field.location.size) @@ -353,6 +344,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: 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), ) diff --git a/compiler/back_end/experimental/rust/generated_code_templates b/compiler/back_end/experimental/rust/generated_code_templates index 2f77dcc8..39daeae7 100644 --- a/compiler/back_end/experimental/rust/generated_code_templates +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -34,8 +34,8 @@ ${field_accessors} } // ** external_field_accessor ** // - pub fn ${field_name}(&self) -> ${type_name}<${bits}, S::Sliced> { - ${type_name}::<${bits}, S::Sliced>::new(self.storage.slice(${byte_offset}, ${byte_length})) + pub fn ${field_name}(&self) -> ${type_name}<${bits}, emboss_runtime::${byte_order}, S::Sliced> { + ${type_name}::<${bits}, emboss_runtime::${byte_order}, S::Sliced>::new(self.storage.slice(${byte_offset}, ${byte_length})) } // ** struct_field_accessor ** // diff --git a/compiler/back_end/experimental/rust/testcode/BUILD b/compiler/back_end/experimental/rust/testcode/BUILD index e447714f..44b0c6b7 100644 --- a/compiler/back_end/experimental/rust/testcode/BUILD +++ b/compiler/back_end/experimental/rust/testcode/BUILD @@ -42,3 +42,17 @@ rust_test( "//runtime/experimental/rust:emboss_runtime", ], ) + +emboss_rust_library( + name = "endian_emb_rs", + deps = ["//testdata:endian_emboss_ir"], +) + +rust_test( + name = "endian_test", + srcs = ["endian_test.rs"], + deps = [ + ":endian_emb_rs", + "//runtime/experimental/rust:emboss_runtime", + ], +) 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..0ade9247 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/endian_test.rs @@ -0,0 +1,51 @@ +// 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 = endian_emb_rs::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 = endian_emb_rs::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)); +} diff --git a/runtime/experimental/rust/src/lib.rs b/runtime/experimental/rust/src/lib.rs index 315979eb..a36b9dd5 100644 --- a/runtime/experimental/rust/src/lib.rs +++ b/runtime/experimental/rust/src/lib.rs @@ -53,17 +53,48 @@ impl Storage for Result { } } -pub trait DecodeFromStorage: Sized { +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; } macro_rules! impl_decode_uint { ($type:ty) => { - impl DecodeFromStorage for $type { + 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) << (i * 8); + val |= (storage.try_read_byte(i)? as $type) << E::shift(i, size_in_bytes); } Ok(val) } @@ -76,7 +107,7 @@ impl_decode_uint!(u32); impl_decode_uint!(u64); pub trait SmallestUInt { - type T: DecodeFromStorage; + type T; } pub struct SizeSelector; @@ -101,20 +132,28 @@ impl_smallest_uint!( u64, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 ); -pub struct UInt { +pub struct UInt { storage: S, + _marker: core::marker::PhantomData, } -impl UInt +impl UInt where SizeSelector: SmallestUInt, + as SmallestUInt>::T: DecodeFromStorage, { pub fn new(storage: S) -> Self { - Self { storage } + Self { + storage, + _marker: core::marker::PhantomData, + } } pub fn try_read(&self) -> Result< as SmallestUInt>::T, Error> { let size_in_bytes = (BITS + 7) / 8; - < as SmallestUInt>::T>::decode(&self.storage, size_in_bytes) + < as SmallestUInt>::T as DecodeFromStorage>::decode( + &self.storage, + size_in_bytes, + ) } } diff --git a/testdata/BUILD b/testdata/BUILD index 2d3f6231..8fd1907a 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 = [ 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"] From 4786ebce294cfd9907aa32fb586358c31190407e Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 05/19] rust: Add Int support --- .../experimental/rust/emboss_codegen_rust.py | 2 +- .../back_end/experimental/rust/testcode/BUILD | 13 +++ .../rust/testcode/int_sizes_test.rs | 60 +++++++++++++ runtime/experimental/rust/src/lib.rs | 90 ++++++++++++++++--- runtime/experimental/rust/src/prelude.rs | 2 +- 5 files changed, 152 insertions(+), 15 deletions(-) create mode 100644 compiler/back_end/experimental/rust/testcode/int_sizes_test.rs diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index c1638fa8..3731a9f0 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -32,7 +32,7 @@ ErrorList = list[list[error._Message]] _TEMPLATE_FILE_NAME = "generated_code_templates" -_UNSUPPORTED_PRELUDE_TYPES = {"Int", "Bcd", "Flag", "Float"} +_UNSUPPORTED_PRELUDE_TYPES = {"Bcd", "Flag", "Float"} _SYNTHETIC_ATTRIBUTES = {"expected_back_ends", "fixed_size_in_bits"} diff --git a/compiler/back_end/experimental/rust/testcode/BUILD b/compiler/back_end/experimental/rust/testcode/BUILD index 44b0c6b7..8c0715d2 100644 --- a/compiler/back_end/experimental/rust/testcode/BUILD +++ b/compiler/back_end/experimental/rust/testcode/BUILD @@ -56,3 +56,16 @@ rust_test( "//runtime/experimental/rust:emboss_runtime", ], ) + +emboss_rust_library( + name = "int_sizes_emb_rs", + deps = ["//testdata:int_sizes_emboss_ir"], +) + +rust_test( + name = "int_sizes_test", + srcs = ["int_sizes_test.rs"], + deps = [ + ":int_sizes_emb_rs", + ], +) 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..6213fb07 --- /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 = int_sizes_emb_rs::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 = int_sizes_emb_rs::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/runtime/experimental/rust/src/lib.rs b/runtime/experimental/rust/src/lib.rs index a36b9dd5..1999512c 100644 --- a/runtime/experimental/rust/src/lib.rs +++ b/runtime/experimental/rust/src/lib.rs @@ -19,27 +19,27 @@ pub enum Error { OutOfBounds, } -pub trait Storage: Clone { - type Sliced: Storage; - fn slice(&self, offset: usize, length: usize) -> Self::Sliced; +pub trait Storage { + type Sliced<'a>: Storage where Self: 'a; + fn slice(&self, offset: usize, length: usize) -> Self::Sliced<'_>; fn try_read_byte(&self, offset: usize) -> Result; } impl<'a, T: ?Sized + AsRef<[u8]>> Storage for &'a T { - type Sliced = Result<&'a [u8], Error>; - fn slice(&self, offset: usize, length: usize) -> Self::Sliced { + type Sliced<'b> = Result<&'b [u8], Error> where Self: 'b; + fn slice(&self, offset: usize, length: usize) -> Self::Sliced<'_> { let bytes = (*self).as_ref(); bytes.get(offset..offset + length).ok_or(Error::OutOfBounds) } fn try_read_byte(&self, offset: usize) -> Result { - let bytes = (*self).as_ref(); + let bytes = self.as_ref(); bytes.get(offset).copied().ok_or(Error::OutOfBounds) } } impl Storage for Result { - type Sliced = Result; - fn slice(&self, offset: usize, length: usize) -> Self::Sliced { + type Sliced<'a> = Result, Error> where Self: 'a; + fn slice(&self, offset: usize, length: usize) -> Self::Sliced<'_> { match self { Ok(s) => Ok(s.slice(offset, length)), Err(e) => Err(*e), @@ -137,18 +137,20 @@ pub struct UInt { _marker: core::marker::PhantomData, } -impl UInt -where - SizeSelector: SmallestUInt, - as SmallestUInt>::T: DecodeFromStorage, -{ +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( @@ -157,3 +159,65 @@ where ) } } + +pub trait SmallestInt { + type T; + type U; + fn sign_extend(raw: Self::U, bits: usize) -> Self::T; +} + +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 + } + } + )+ + }; +} + +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)) + } +} diff --git a/runtime/experimental/rust/src/prelude.rs b/runtime/experimental/rust/src/prelude.rs index 0177e32e..1d4c5f91 100644 --- a/runtime/experimental/rust/src/prelude.rs +++ b/runtime/experimental/rust/src/prelude.rs @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub use crate::UInt; +pub use crate::{Int, UInt}; From 268b9c1eeeb1a12bbc9be3abe3c942262f382484 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 06/19] rust: Add try_write support --- .../experimental/rust/emboss_codegen_rust.py | 22 +++++ .../rust/generated_code_templates | 29 +++++- .../experimental/rust/testcode/endian_test.rs | 27 ++++++ runtime/experimental/rust/src/lib.rs | 93 +++++++++++++++++++ 4 files changed, 167 insertions(+), 4 deletions(-) diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index 3731a9f0..180a1074 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -195,6 +195,7 @@ def _generate_type(type_ir, ir, module, templates, diagnostics) -> str: def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: struct_name = type_ir.name.name.text field_accessors = [] + mut_field_accessors = [] for field in type_ir.structure.field: field_name = field.name.name.text @@ -349,6 +350,17 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: 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( @@ -359,6 +371,15 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: 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.replace(".", "::"), + byte_offset=str(byte_offset), + byte_length=str(byte_length), + ) + ) else: diagnostics.append( [ @@ -379,6 +400,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: templates.struct_view, struct_name=struct_name, field_accessors="".join(field_accessors), + mut_field_accessors="".join(mut_field_accessors), ) diff --git a/compiler/back_end/experimental/rust/generated_code_templates b/compiler/back_end/experimental/rust/generated_code_templates index 39daeae7..e721807c 100644 --- a/compiler/back_end/experimental/rust/generated_code_templates +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -17,7 +17,7 @@ #![allow(dead_code, unused_imports, unused_variables, non_camel_case_types)] use emboss_runtime::prelude::*; -use emboss_runtime::{Error, Storage}; +use emboss_runtime::{Error, Storage, MutStorage}; ${struct_definitions} @@ -33,12 +33,33 @@ impl ${struct_name} { ${field_accessors} } +pub struct ${struct_name}Mut { + storage: S, +} + +impl ${struct_name}Mut { + pub fn new(storage: S) -> Self { + Self { storage } + } +${mut_field_accessors} +} + // ** external_field_accessor ** // - pub fn ${field_name}(&self) -> ${type_name}<${bits}, emboss_runtime::${byte_order}, S::Sliced> { - ${type_name}::<${bits}, emboss_runtime::${byte_order}, S::Sliced>::new(self.storage.slice(${byte_offset}, ${byte_length})) + pub fn ${field_name}(&self) -> ${type_name}<${bits}, emboss_runtime::${byte_order}, S::Sliced<'_>> { + ${type_name}::<${bits}, emboss_runtime::${byte_order}, S::Sliced<'_>>::new(self.storage.slice(${byte_offset}, ${byte_length})) + } + +// ** external_mut_field_accessor ** // + pub fn ${field_name}(&mut self) -> ${type_name}<${bits}, emboss_runtime::${byte_order}, S::SlicedMut<'_>> { + ${type_name}::<${bits}, emboss_runtime::${byte_order}, S::SlicedMut<'_>>::new(self.storage.slice_mut(${byte_offset}, ${byte_length})) } // ** struct_field_accessor ** // - pub fn ${field_name}(&self) -> ${type_name} { + pub fn ${field_name}(&self) -> ${type_name}> { ${type_name}::new(self.storage.slice(${byte_offset}, ${byte_length})) } + +// ** struct_mut_field_accessor ** // + pub fn ${field_name}(&mut self) -> ${type_name}Mut> { + ${type_name}Mut::new(self.storage.slice_mut(${byte_offset}, ${byte_length})) + } diff --git a/compiler/back_end/experimental/rust/testcode/endian_test.rs b/compiler/back_end/experimental/rust/testcode/endian_test.rs index 0ade9247..ef48664c 100644 --- a/compiler/back_end/experimental/rust/testcode/endian_test.rs +++ b/compiler/back_end/experimental/rust/testcode/endian_test.rs @@ -49,3 +49,30 @@ fn handles_endian_out_of_bounds() { 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 = endian_emb_rs::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 = endian_emb_rs::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/runtime/experimental/rust/src/lib.rs b/runtime/experimental/rust/src/lib.rs index 1999512c..9cdad0a8 100644 --- a/runtime/experimental/rust/src/lib.rs +++ b/runtime/experimental/rust/src/lib.rs @@ -25,6 +25,24 @@ pub trait Storage { 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) -> Self::SlicedMut<'_>; + 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) -> Self::Sliced<'_> { + let bytes = self.as_ref(); + 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) -> Self::Sliced<'_> { @@ -37,6 +55,20 @@ impl<'a, T: ?Sized + AsRef<[u8]>> Storage for &'a T { } } +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) -> Self::SlicedMut<'_> { + let bytes = self.as_mut(); + 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) -> Self::Sliced<'_> { @@ -53,6 +85,22 @@ impl Storage for Result { } } +impl MutStorage for Result { + type SlicedMut<'a> = Result, Error> where Self: 'a; + fn slice_mut(&mut self, offset: usize, length: usize) -> Self::SlicedMut<'_> { + match self { + Ok(s) => Ok(s.slice_mut(offset, length)), + Err(e) => Err(*e), // Forwards the previous failure transparently + } + } + 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; } @@ -88,6 +136,10 @@ 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 { @@ -99,6 +151,14 @@ macro_rules! impl_decode_uint { 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); @@ -160,10 +220,22 @@ where } } +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 { @@ -178,6 +250,15 @@ macro_rules! impl_smallest_int { 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 + } } )+ }; @@ -221,3 +302,15 @@ where 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) + } +} From 6ac1f2a0a887678c919c85861506314878d96d41 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 07/19] rust: Add enum support --- .../experimental/rust/emboss_codegen_rust.py | 85 +++++++++++ .../rust/generated_code_templates | 50 +++++++ .../back_end/experimental/rust/testcode/BUILD | 14 ++ .../experimental/rust/testcode/enum_test.rs | 63 +++++++++ runtime/experimental/rust/src/lib.rs | 132 +++++++++++++++++- 5 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 compiler/back_end/experimental/rust/testcode/enum_test.rs diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index 180a1074..7da9d967 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -125,6 +125,9 @@ def _generate_type(type_ir, ir, module, templates, diagnostics) -> str: if type_ir.has_field("external"): return "" + if type_ir.has_field("enumeration"): + return _generate_enum(type_ir, ir, module, templates, diagnostics) + if type_ir.runtime_parameter: diagnostics.append( [ @@ -192,6 +195,62 @@ def _generate_type(type_ir, ir, module, templates, diagnostics) -> str: return "".join(definitions) +def _generate_enum(type_ir, ir, module, templates, diagnostics) -> str: + enum_name = type_ir.name.name.text + 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 _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: struct_name = type_ir.name.name.text field_accessors = [] @@ -380,6 +439,32 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: byte_length=str(byte_length), ) ) + elif referenced_type.has_field("enumeration"): + bits = byte_length * 8 + if field.type.has_field("size_in_bits"): + bits = ir_util.constant_value(field.type.size_in_bits) + field_accessors.append( + code_template.format_template( + templates.enum_field_accessor, + field_name=field_name, + enum_name=source_name.replace(".", "::"), + 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.replace(".", "::"), + bits=str(bits), + byte_order=byte_order, + byte_offset=str(byte_offset), + byte_length=str(byte_length), + ) + ) else: diagnostics.append( [ diff --git a/compiler/back_end/experimental/rust/generated_code_templates b/compiler/back_end/experimental/rust/generated_code_templates index e721807c..75d4a592 100644 --- a/compiler/back_end/experimental/rust/generated_code_templates +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -63,3 +63,53 @@ ${mut_field_accessors} pub fn ${field_name}(&mut self) -> ${type_name}Mut> { ${type_name}Mut::new(self.storage.slice_mut(${byte_offset}, ${byte_length})) } + +// ** 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}), + +// ** enum_field_accessor ** // + pub fn ${field_name}(&self) -> emboss_runtime::EnumView<${enum_name}, emboss_runtime::UInt<${bits}, emboss_runtime::${byte_order}, S::Sliced<'_>>> { + emboss_runtime::EnumView::new(emboss_runtime::UInt::new(self.storage.slice(${byte_offset}, ${byte_length}))) + } + +// ** enum_mut_field_accessor ** // + pub fn ${field_name}(&mut self) -> emboss_runtime::EnumViewMut<${enum_name}, emboss_runtime::UInt<${bits}, emboss_runtime::${byte_order}, S::SlicedMut<'_>>> { + emboss_runtime::EnumViewMut::new(emboss_runtime::UInt::new(self.storage.slice_mut(${byte_offset}, ${byte_length}))) + } + diff --git a/compiler/back_end/experimental/rust/testcode/BUILD b/compiler/back_end/experimental/rust/testcode/BUILD index 8c0715d2..b8da0f6a 100644 --- a/compiler/back_end/experimental/rust/testcode/BUILD +++ b/compiler/back_end/experimental/rust/testcode/BUILD @@ -69,3 +69,17 @@ rust_test( ":int_sizes_emb_rs", ], ) + +emboss_rust_library( + name = "enum_emb_rs", + deps = ["//testdata:enum_emboss_ir"], +) + +rust_test( + name = "enum_test", + srcs = ["enum_test.rs"], + deps = [ + ":enum_emb_rs", + "//runtime/experimental/rust:emboss_runtime", + ], +) 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..eb3f20e1 --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/enum_test.rs @@ -0,0 +1,63 @@ +pub use enum_emb_rs::*; + +#[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/runtime/experimental/rust/src/lib.rs b/runtime/experimental/rust/src/lib.rs index 9cdad0a8..8254d9ca 100644 --- a/runtime/experimental/rust/src/lib.rs +++ b/runtime/experimental/rust/src/lib.rs @@ -15,8 +15,18 @@ pub mod prelude; #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Error { +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 { @@ -314,3 +324,123 @@ where 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) + } +} From cb8e6eda68bf1f4806ac7f933953671d3cb51c7d Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 08/19] rust: Support dynamic expressions --- .../experimental/rust/emboss_codegen_rust.py | 76 +++++++++++-- .../rust/generated_code_templates | 104 +++++++++++++++--- .../back_end/experimental/rust/testcode/BUILD | 14 +++ .../rust/testcode/dynamic_size_test.rs | 53 +++++++++ runtime/experimental/rust/src/lib.rs | 36 +++--- 5 files changed, 246 insertions(+), 37 deletions(-) create mode 100644 compiler/back_end/experimental/rust/testcode/dynamic_size_test.rs diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index 7da9d967..8d537521 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -251,12 +251,61 @@ def _generate_enum(type_ir, ir, module, templates, diagnostics) -> str: ) +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"): + if len(expr.field_reference.path) > 1: + return None + ref_name = expr.field_reference.path[0].canonical_name.object_path[-1] + if ref_name not in generated_fields: + return None + return code_template.format_template(templates.expr_field_reference, self_ref=self_ref, field_name=ref_name) + + 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.MINIMUM.value: + return code_template.format_template(templates.expr_minimum, left=args[0], right=args[1]) + if func == ir_data.FunctionMapping.CHOICE.value: + return code_template.format_template(templates.expr_choice, cond=args[0], true_branch=args[1], false_branch=args[2]) + + return None + return None + + def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: struct_name = type_ir.name.name.text field_accessors = [] mut_field_accessors = [] + 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 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 @@ -320,15 +369,17 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: ) continue - if not ir_util.is_constant(field.location.start) or not ir_util.is_constant( - field.location.size - ): + 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 size or offset is not yet supported in this backend. Field '{field_name}' will be omitted.", + f"Non-constant {reason} relies on unsupported expression or omitted field. Field '{field_name}' will be omitted.", ) ] ) @@ -374,8 +425,13 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: else: byte_order = "Null" - byte_offset = ir_util.constant_value(field.location.start) - byte_length = ir_util.constant_value(field.location.size) + 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(field.type.atomic_type.reference, ir) @@ -394,7 +450,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: continue if referenced_type.has_field("external"): - bits = byte_length * 8 + bits = const_byte_length * 8 if field.type.has_field("size_in_bits"): bits = ir_util.constant_value(field.type.size_in_bits) @@ -440,7 +496,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: ) ) elif referenced_type.has_field("enumeration"): - bits = byte_length * 8 + bits = const_byte_length * 8 if field.type.has_field("size_in_bits"): bits = ir_util.constant_value(field.type.size_in_bits) field_accessors.append( @@ -480,6 +536,8 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: ] ) continue + + generated_fields.add(field_name) return code_template.format_template( templates.struct_view, diff --git a/compiler/back_end/experimental/rust/generated_code_templates b/compiler/back_end/experimental/rust/generated_code_templates index 75d4a592..c86a3b43 100644 --- a/compiler/back_end/experimental/rust/generated_code_templates +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -45,23 +45,59 @@ ${mut_field_accessors} } // ** external_field_accessor ** // - pub fn ${field_name}(&self) -> ${type_name}<${bits}, emboss_runtime::${byte_order}, S::Sliced<'_>> { - ${type_name}::<${bits}, emboss_runtime::${byte_order}, S::Sliced<'_>>::new(self.storage.slice(${byte_offset}, ${byte_length})) + 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}; + let len = ${byte_length}; + 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}, S::SlicedMut<'_>> { - ${type_name}::<${bits}, emboss_runtime::${byte_order}, S::SlicedMut<'_>>::new(self.storage.slice_mut(${byte_offset}, ${byte_length})) + 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}; + let len = ${byte_length}; + 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}> { - ${type_name}::new(self.storage.slice(${byte_offset}, ${byte_length})) + 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}; + let len = ${byte_length}; + 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> { - ${type_name}Mut::new(self.storage.slice_mut(${byte_offset}, ${byte_length})) + 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}; + let len = ${byte_length}; + 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 ** // @@ -103,13 +139,55 @@ ${enum_aliases} // ** enum_match_variant ** // ${variant_value} => Ok(${enum_name}::${variant_name}), +// ** expr_addition ** // +(${left} + ${right}) + +// ** expr_subtraction ** // +(${left} - ${right}) + +// ** expr_multiplication ** // +(${left} * ${right}) + +// ** expr_constant ** // +${constant_value} + +// ** expr_field_reference ** // +({ let val = ${self_ref}.${field_name}().try_read()?; val as usize }) + +// ** expr_maximum ** // +core::cmp::max(${left}, ${right}) + +// ** expr_minimum ** // +core::cmp::min(${left}, ${right}) + +// ** 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}, S::Sliced<'_>>> { - emboss_runtime::EnumView::new(emboss_runtime::UInt::new(self.storage.slice(${byte_offset}, ${byte_length}))) + 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}; + let len = ${byte_length}; + 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}, S::SlicedMut<'_>>> { - emboss_runtime::EnumViewMut::new(emboss_runtime::UInt::new(self.storage.slice_mut(${byte_offset}, ${byte_length}))) + 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}; + let len = ${byte_length}; + 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)) } - diff --git a/compiler/back_end/experimental/rust/testcode/BUILD b/compiler/back_end/experimental/rust/testcode/BUILD index b8da0f6a..15a4aacd 100644 --- a/compiler/back_end/experimental/rust/testcode/BUILD +++ b/compiler/back_end/experimental/rust/testcode/BUILD @@ -83,3 +83,17 @@ rust_test( "//runtime/experimental/rust:emboss_runtime", ], ) + +emboss_rust_library( + name = "dynamic_size_emb_rs", + deps = ["//testdata:dynamic_size_emboss_ir"], +) + +rust_test( + name = "dynamic_size_test", + srcs = ["dynamic_size_test.rs"], + deps = [ + ":dynamic_size_emb_rs", + "//runtime/experimental/rust:emboss_runtime", + ], +) 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..8d6b87bd --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/dynamic_size_test.rs @@ -0,0 +1,53 @@ +pub use dynamic_size_emb_rs::*; +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/runtime/experimental/rust/src/lib.rs b/runtime/experimental/rust/src/lib.rs index 8254d9ca..9ddd8c0b 100644 --- a/runtime/experimental/rust/src/lib.rs +++ b/runtime/experimental/rust/src/lib.rs @@ -31,21 +31,21 @@ impl Error { pub trait Storage { type Sliced<'a>: Storage where Self: 'a; - fn slice(&self, offset: usize, length: usize) -> Self::Sliced<'_>; + 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) -> Self::SlicedMut<'_>; + 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) -> Self::Sliced<'_> { + fn slice(&self, offset: usize, length: usize) -> Result, Error> { let bytes = self.as_ref(); - bytes.get(offset..offset + length).ok_or(Error::OutOfBounds) + Ok(bytes.get(offset..offset + length).ok_or(Error::OutOfBounds)) } fn try_read_byte(&self, offset: usize) -> Result { let bytes = self.as_ref(); @@ -55,9 +55,9 @@ impl<'a, T: ?Sized + AsRef<[u8]>> Storage for &'a mut T { 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) -> Self::Sliced<'_> { + fn slice(&self, offset: usize, length: usize) -> Result, Error> { let bytes = (*self).as_ref(); - bytes.get(offset..offset + length).ok_or(Error::OutOfBounds) + Ok(bytes.get(offset..offset + length).ok_or(Error::OutOfBounds)) } fn try_read_byte(&self, offset: usize) -> Result { let bytes = self.as_ref(); @@ -67,9 +67,9 @@ impl<'a, T: ?Sized + AsRef<[u8]>> Storage for &'a T { 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) -> Self::SlicedMut<'_> { + fn slice_mut(&mut self, offset: usize, length: usize) -> Result, Error> { let bytes = self.as_mut(); - bytes.get_mut(offset..offset + length).ok_or(Error::OutOfBounds) + 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(); @@ -81,9 +81,12 @@ impl<'a, T: ?Sized + AsMut<[u8]> + AsRef<[u8]>> MutStorage for &'a mut T { impl Storage for Result { type Sliced<'a> = Result, Error> where Self: 'a; - fn slice(&self, offset: usize, length: usize) -> Self::Sliced<'_> { + fn slice(&self, offset: usize, length: usize) -> Result, Error> { match self { - Ok(s) => Ok(s.slice(offset, length)), + Ok(s) => match s.slice(offset, length) { + Ok(sliced) => Ok(Ok(sliced)), + Err(e) => Err(e), + }, Err(e) => Err(*e), } } @@ -97,10 +100,13 @@ impl Storage for Result { impl MutStorage for Result { type SlicedMut<'a> = Result, Error> where Self: 'a; - fn slice_mut(&mut self, offset: usize, length: usize) -> Self::SlicedMut<'_> { + fn slice_mut(&mut self, offset: usize, length: usize) -> Result, Error> { match self { - Ok(s) => Ok(s.slice_mut(offset, length)), - Err(e) => Err(*e), // Forwards the previous failure transparently + 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> { @@ -307,7 +313,7 @@ where { 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 slice = self.storage.slice(0, size_in_bytes)?; let raw = UInt::>::new(slice).try_read()?; Ok( as SmallestInt>::sign_extend(raw, BITS)) } @@ -320,7 +326,7 @@ where { 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)); + let mut uint_view = UInt::>::new(self.storage.slice_mut(0, (BITS + 7) / 8)?); uint_view.try_write(unsigned) } } From 57b44fb8837c32213f3f99dec67a6df485f781a0 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 09/19] rust: Add array support --- .../experimental/rust/emboss_codegen_rust.py | 295 +++++++++++++----- .../rust/generated_code_templates | 113 ++++++- .../back_end/experimental/rust/testcode/BUILD | 14 + .../rust/testcode/auto_array_size_test.rs | 28 ++ 4 files changed, 362 insertions(+), 88 deletions(-) create mode 100644 compiler/back_end/experimental/rust/testcode/auto_array_size_test.rs diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index 8d537521..52dbeda9 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -292,10 +292,96 @@ def _generate_expression(expr, ir, module, generated_fields, templates, self_ref 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"): + struct_clean_name = source_name.replace(".", "::") + element_type = code_template.format_template(templates.array_element_type_structure, struct_clean_name=struct_clean_name, storage_type=storage_type) + element_type_mut = code_template.format_template(templates.array_element_type_structure, struct_clean_name=struct_clean_name + "Mut", storage_type=storage_type_mut) + element_constructor = code_template.format_template(templates.array_element_constructor_structure, struct_clean_name=struct_clean_name) + element_constructor_mut = code_template.format_template(templates.array_element_constructor_structure, struct_clean_name=struct_clean_name + "Mut") + elif referenced_type.has_field("enumeration"): + enum_clean_name = source_name.replace(".", "::") + element_type = code_template.format_template(templates.array_element_type_enumeration, enum_clean_name=enum_clean_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=enum_clean_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) -> str: struct_name = type_ir.name.name.text field_accessors = [] mut_field_accessors = [] + generated_nested_types = [] generated_fields = set() fields_to_process = [] @@ -326,7 +412,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: ) continue - if not field.has_field("type") or not field.type.has_field("atomic_type"): + 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") @@ -337,13 +423,31 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: error.warn( module.source_file_name, loc, - f"Non-atomic types are not yet supported in this backend. Field '{field_name}' will be omitted.", + 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 - source_name = field.type.atomic_type.reference.source_name[0].text + source_name = base_type.atomic_type.reference.source_name[0].text if source_name in _UNSUPPORTED_PRELUDE_TYPES: diagnostics.append( @@ -433,7 +537,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: else: const_byte_length = 0 - referenced_type = ir_util.find_object(field.type.atomic_type.reference, ir) + referenced_type = ir_util.find_object(base_type.atomic_type.reference, ir) if referenced_type not in module.type and not referenced_type.has_field( "external" @@ -449,102 +553,123 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: ) continue - if referenced_type.has_field("external"): - bits = const_byte_length * 8 - if field.type.has_field("size_in_bits"): - bits = ir_util.constant_value(field.type.size_in_bits) - - 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), - ) + 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, ) - 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), + else: + if referenced_type.has_field("external"): + bits = const_byte_length * 8 + if field.type.has_field("size_in_bits"): + bits = ir_util.constant_value(field.type.size_in_bits) + + 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), + ) ) - ) - 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.replace(".", "::"), - 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), + ) ) - ) - mut_field_accessors.append( - code_template.format_template( - templates.struct_mut_field_accessor, - field_name=field_name, - type_name=source_name.replace(".", "::"), - 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.replace(".", "::"), + byte_offset=str(byte_offset), + byte_length=str(byte_length), + ) ) - ) - elif referenced_type.has_field("enumeration"): - bits = const_byte_length * 8 - if field.type.has_field("size_in_bits"): - bits = ir_util.constant_value(field.type.size_in_bits) - field_accessors.append( - code_template.format_template( - templates.enum_field_accessor, - field_name=field_name, - enum_name=source_name.replace(".", "::"), - 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.struct_mut_field_accessor, + field_name=field_name, + type_name=source_name.replace(".", "::"), + 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.replace(".", "::"), - bits=str(bits), - byte_order=byte_order, - byte_offset=str(byte_offset), - byte_length=str(byte_length), + elif referenced_type.has_field("enumeration"): + bits = const_byte_length * 8 + if field.type.has_field("size_in_bits"): + bits = ir_util.constant_value(field.type.size_in_bits) + field_accessors.append( + code_template.format_template( + templates.enum_field_accessor, + field_name=field_name, + enum_name=source_name.replace(".", "::"), + 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.", + mut_field_accessors.append( + code_template.format_template( + templates.enum_mut_field_accessor, + field_name=field_name, + enum_name=source_name.replace(".", "::"), + bits=str(bits), + byte_order=byte_order, + byte_offset=str(byte_offset), + byte_length=str(byte_length), ) - ] - ) - continue + ) + 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) - return code_template.format_template( + 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( diff --git a/compiler/back_end/experimental/rust/generated_code_templates b/compiler/back_end/experimental/rust/generated_code_templates index c86a3b43..755aba70 100644 --- a/compiler/back_end/experimental/rust/generated_code_templates +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -14,10 +14,10 @@ // ** rust_module ** /////////////////////////////////////////////////////////// // Generated by the Emboss compiler. DO NOT EDIT! -#![allow(dead_code, unused_imports, unused_variables, non_camel_case_types)] +#![allow(dead_code, unused_imports, unused_variables, non_camel_case_types, unused_parens)] use emboss_runtime::prelude::*; -use emboss_runtime::{Error, Storage, MutStorage}; +pub use emboss_runtime::{Error, Storage, MutStorage, TryRead, TryWrite, UInt}; ${struct_definitions} @@ -152,7 +152,7 @@ ${enum_aliases} ${constant_value} // ** expr_field_reference ** // -({ let val = ${self_ref}.${field_name}().try_read()?; val as usize }) +{ let val = ${self_ref}.${field_name}().try_read()?; val as usize } // ** expr_maximum ** // core::cmp::max(${left}, ${right}) @@ -191,3 +191,110 @@ core::cmp::min(${left}, ${right}) }; 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}; + let len = ${byte_length}; + let element_count = len / (${element_size}); + 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}; + let len = ${byte_length}; + let element_count = len / (${element_size}); + 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)) + + diff --git a/compiler/back_end/experimental/rust/testcode/BUILD b/compiler/back_end/experimental/rust/testcode/BUILD index 15a4aacd..60110aff 100644 --- a/compiler/back_end/experimental/rust/testcode/BUILD +++ b/compiler/back_end/experimental/rust/testcode/BUILD @@ -97,3 +97,17 @@ rust_test( "//runtime/experimental/rust:emboss_runtime", ], ) + +emboss_rust_library( + name = "auto_array_size_emb_rs", + deps = ["//testdata:auto_array_size_emboss_ir"], +) + +rust_test( + name = "auto_array_size_test", + srcs = ["auto_array_size_test.rs"], + deps = [ + ":auto_array_size_emb_rs", + "//runtime/experimental/rust:emboss_runtime", + ], +) 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..1ce7fb0e --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/auto_array_size_test.rs @@ -0,0 +1,28 @@ +use auto_array_size_emb_rs::*; + +#[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()); +} From 14e8b0389786072f15440ea0c791ca0ae0a47957 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 10/19] rust: Support imported types --- build_defs.bzl | 48 +++++++++++-- .../experimental/rust/emboss_codegen_rust.py | 66 +++++++---------- .../rust/generated_code_templates | 2 + .../back_end/experimental/rust/testcode/BUILD | 72 +++++-------------- .../rust/testcode/auto_array_size_test.rs | 2 +- .../rust/testcode/dynamic_size_test.rs | 2 +- .../experimental/rust/testcode/endian_test.rs | 8 +-- .../experimental/rust/testcode/enum_test.rs | 2 +- .../rust/testcode/imported_test.rs | 2 +- .../rust/testcode/importer_test.rs | 15 ++++ .../rust/testcode/int_sizes_test.rs | 4 +- .../rust/testcode/nested_structure_test.rs | 4 +- .../rust/testcode/next_keyword_test.rs | 2 +- testdata/BUILD | 58 +++++++++++++++ 14 files changed, 173 insertions(+), 114 deletions(-) create mode 100644 compiler/back_end/experimental/rust/testcode/importer_test.rs diff --git a/build_defs.bzl b/build_defs.bzl index 46922bc0..12a34e8c 100644 --- a/build_defs.bzl +++ b/build_defs.bzl @@ -316,25 +316,63 @@ _rust_emboss_codegen = rule( }, ) -def emboss_rust_library(name, deps, rust_deps = [], **kwargs): - """Constructs a Rust library from an emboss_library target. +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. - deps: A list containing exactly one emboss_library dependency. + 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 = deps, + 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], - deps = rust_deps + ["//runtime/experimental/rust:emboss_runtime"], + crate_name = crate_name, + deps = rust_deps + deps + ["//runtime/experimental/rust:emboss_runtime"], **kwargs ) diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index 52dbeda9..eb098bef 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -73,46 +73,42 @@ def generate_code(ir: ir_data.EmbossIr) -> tuple[Source, Diagnostics]: ] ) + imports_list = [] struct_definitions = [] - for module in ir.module: + 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") - real_imports = [ - imp for imp in module.foreign_import if imp.file_name.text != "" - ] - if real_imports: + 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, - real_imports[0].source_location, - f"Imports are not yet supported in this backend. Foreign imports will be omitted.", + attr.source_location, + f"Module-level attribute '{attr.name.text}' is not yet supported in this backend. It will be omitted.", ) ] ) - 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) - ) + 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), ) @@ -447,7 +443,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: ) continue - source_name = base_type.atomic_type.reference.source_name[0].text + source_name = "::".join(part.text for part in base_type.atomic_type.reference.source_name) if source_name in _UNSUPPORTED_PRELUDE_TYPES: diagnostics.append( @@ -539,20 +535,6 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: referenced_type = ir_util.find_object(base_type.atomic_type.reference, ir) - if referenced_type not in module.type and not referenced_type.has_field( - "external" - ): - diagnostics.append( - [ - error.warn( - module.source_file_name, - field.source_location, - f"Cross-module dependencies are not yet supported in this backend. Field '{field_name}' will be omitted.", - ) - ] - ) - continue - if is_array: # Generate a unique struct for each array field. _generate_array_field( diff --git a/compiler/back_end/experimental/rust/generated_code_templates b/compiler/back_end/experimental/rust/generated_code_templates index 755aba70..6761c47f 100644 --- a/compiler/back_end/experimental/rust/generated_code_templates +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -19,6 +19,8 @@ use emboss_runtime::prelude::*; pub use emboss_runtime::{Error, Storage, MutStorage, TryRead, TryWrite, UInt}; +${imports_list} + ${struct_definitions} // ** struct_view ** // diff --git a/compiler/back_end/experimental/rust/testcode/BUILD b/compiler/back_end/experimental/rust/testcode/BUILD index 60110aff..f7f081a5 100644 --- a/compiler/back_end/experimental/rust/testcode/BUILD +++ b/compiler/back_end/experimental/rust/testcode/BUILD @@ -1,113 +1,77 @@ load("@rules_rust//rust:defs.bzl", "rust_test") load("//:build_defs.bzl", "emboss_rust_library") - -emboss_rust_library( - name = "imported_emb_rs", - deps = ["//testdata:imported_emboss_ir"], -) - -emboss_rust_library( - name = "next_keyword_emb_rs", - deps = ["//testdata:next_keyword_emboss_ir"], -) - rust_test( name = "imported_test", srcs = ["imported_test.rs"], deps = [ - ":imported_emb_rs", "//runtime/experimental/rust:emboss_runtime", + "//testdata:imported_emb_rs", ], ) rust_test( - name = "next_keyword_test", - srcs = ["next_keyword_test.rs"], + name = "importer_test", + srcs = ["importer_test.rs"], deps = [ - ":next_keyword_emb_rs", "//runtime/experimental/rust:emboss_runtime", + "//testdata:imported_emb_rs", + "//testdata:importer_emb_rs", ], ) -emboss_rust_library( - name = "nested_structure_emb_rs", - deps = ["//testdata:nested_structure_emboss_ir"], +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 = [ - ":nested_structure_emb_rs", + "//testdata:nested_structure_emb_rs", "//runtime/experimental/rust:emboss_runtime", ], ) - -emboss_rust_library( - name = "endian_emb_rs", - deps = ["//testdata:endian_emboss_ir"], -) - rust_test( name = "endian_test", srcs = ["endian_test.rs"], deps = [ - ":endian_emb_rs", + "//testdata:endian_emb_rs", "//runtime/experimental/rust:emboss_runtime", ], ) - -emboss_rust_library( - name = "int_sizes_emb_rs", - deps = ["//testdata:int_sizes_emboss_ir"], -) - rust_test( name = "int_sizes_test", srcs = ["int_sizes_test.rs"], deps = [ - ":int_sizes_emb_rs", + "//testdata:int_sizes_emb_rs", ], ) - -emboss_rust_library( - name = "enum_emb_rs", - deps = ["//testdata:enum_emboss_ir"], -) - rust_test( name = "enum_test", srcs = ["enum_test.rs"], deps = [ - ":enum_emb_rs", "//runtime/experimental/rust:emboss_runtime", + "//testdata:enum_emb_rs", ], ) - -emboss_rust_library( - name = "dynamic_size_emb_rs", - deps = ["//testdata:dynamic_size_emboss_ir"], -) - rust_test( name = "dynamic_size_test", srcs = ["dynamic_size_test.rs"], deps = [ - ":dynamic_size_emb_rs", "//runtime/experimental/rust:emboss_runtime", + "//testdata:dynamic_size_emb_rs", ], ) - -emboss_rust_library( - name = "auto_array_size_emb_rs", - deps = ["//testdata:auto_array_size_emboss_ir"], -) - rust_test( name = "auto_array_size_test", srcs = ["auto_array_size_test.rs"], deps = [ - ":auto_array_size_emb_rs", "//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 index 1ce7fb0e..4e5d02de 100644 --- a/compiler/back_end/experimental/rust/testcode/auto_array_size_test.rs +++ b/compiler/back_end/experimental/rust/testcode/auto_array_size_test.rs @@ -1,4 +1,4 @@ -use auto_array_size_emb_rs::*; +use testdata_auto_array_size_emb::*; #[test] fn test_array_access() { diff --git a/compiler/back_end/experimental/rust/testcode/dynamic_size_test.rs b/compiler/back_end/experimental/rust/testcode/dynamic_size_test.rs index 8d6b87bd..87c97931 100644 --- a/compiler/back_end/experimental/rust/testcode/dynamic_size_test.rs +++ b/compiler/back_end/experimental/rust/testcode/dynamic_size_test.rs @@ -1,4 +1,4 @@ -pub use dynamic_size_emb_rs::*; +pub use testdata_dynamic_size_emb::*; use emboss_runtime::{prelude::*, Error}; #[test] diff --git a/compiler/back_end/experimental/rust/testcode/endian_test.rs b/compiler/back_end/experimental/rust/testcode/endian_test.rs index ef48664c..8081b940 100644 --- a/compiler/back_end/experimental/rust/testcode/endian_test.rs +++ b/compiler/back_end/experimental/rust/testcode/endian_test.rs @@ -22,7 +22,7 @@ fn reads_endian_values_correctly() { 0xAB, // 8:9 single_byte == 0xAB ]; - let view = endian_emb_rs::Message::new(container); + let view = testdata_endian_emb::Message::new(container); let little_val: u32 = view.little_uint32().try_read().unwrap(); assert_eq!(little_val, 0x12345678); @@ -41,7 +41,7 @@ fn handles_endian_out_of_bounds() { 0x12, 0x34, 0x56, // TRUNCATED 4:7 ]; - let view = endian_emb_rs::Message::new(container); + let view = testdata_endian_emb::Message::new(container); let little_val: u32 = view.little_uint32().try_read().unwrap(); assert_eq!(little_val, 0x12345678); @@ -54,7 +54,7 @@ fn handles_endian_out_of_bounds() { fn writes_endian_values_correctly() { let mut container: [u8; 9] = [0; 9]; { - let mut view = endian_emb_rs::MessageMut::new(&mut container[..]); + 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(); @@ -70,7 +70,7 @@ fn writes_endian_values_correctly() { #[test] fn handles_endian_write_out_of_bounds() { let mut container: [u8; 7] = [0; 7]; - let mut view = endian_emb_rs::MessageMut::new(&mut container[..]); + 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)); diff --git a/compiler/back_end/experimental/rust/testcode/enum_test.rs b/compiler/back_end/experimental/rust/testcode/enum_test.rs index eb3f20e1..d170c8ed 100644 --- a/compiler/back_end/experimental/rust/testcode/enum_test.rs +++ b/compiler/back_end/experimental/rust/testcode/enum_test.rs @@ -1,4 +1,4 @@ -pub use enum_emb_rs::*; +pub use testdata_enum_emb::*; #[test] fn generates_correct_enum_variants() { diff --git a/compiler/back_end/experimental/rust/testcode/imported_test.rs b/compiler/back_end/experimental/rust/testcode/imported_test.rs index 0daae3a4..4d7d93c6 100644 --- a/compiler/back_end/experimental/rust/testcode/imported_test.rs +++ b/compiler/back_end/experimental/rust/testcode/imported_test.rs @@ -1,5 +1,5 @@ use emboss_runtime::{prelude::*, Error}; -use imported_emb_rs::*; +use testdata_imported_emb::*; #[cfg(test)] mod tests { 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..f72b065c --- /dev/null +++ b/compiler/back_end/experimental/rust/testcode/importer_test.rs @@ -0,0 +1,15 @@ +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 index 6213fb07..5d633ee1 100644 --- a/compiler/back_end/experimental/rust/testcode/int_sizes_test.rs +++ b/compiler/back_end/experimental/rust/testcode/int_sizes_test.rs @@ -29,7 +29,7 @@ fn reads_int_sizes_correctly() { 0xfd, 0xfe, 0xff, 0x80, // 32:36 eight_byte == -0x7f00010203040506 ]; - let view = int_sizes_emb_rs::Sizes::new(container); + 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); @@ -47,7 +47,7 @@ fn reads_int_sizes_correctly() { #[test] fn reads_negative_ones_correctly() { let container: &[u8] = &[0xff; 36]; - let view = int_sizes_emb_rs::Sizes::new(container); + 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); diff --git a/compiler/back_end/experimental/rust/testcode/nested_structure_test.rs b/compiler/back_end/experimental/rust/testcode/nested_structure_test.rs index 3b43d697..9a7bb166 100644 --- a/compiler/back_end/experimental/rust/testcode/nested_structure_test.rs +++ b/compiler/back_end/experimental/rust/testcode/nested_structure_test.rs @@ -10,7 +10,7 @@ fn container_field_values_are_correct() { 0xcc, 0xbb, 0xaa, 0x00, // 16:20 other_box.count == 0xaabbcc ]; - let view = nested_structure_emb_rs::Container::new(container); + let view = testdata_nested_structure_emb::Container::new(container); // Static explicit typing assertions let weight: u32 = view.weight().try_read().unwrap(); @@ -37,7 +37,7 @@ fn nested_out_of_bounds_handles_cascading_reads_dynamically() { 0x03, 0x02, 0x01, 0x00, // 8:12 important_box.count == 0x010203 ]; // TRUNCATED struct! Missing the entire 8 bytes of other_box! - let view = nested_structure_emb_rs::Container::new(container); + 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); diff --git a/compiler/back_end/experimental/rust/testcode/next_keyword_test.rs b/compiler/back_end/experimental/rust/testcode/next_keyword_test.rs index d985e2b9..6d65c6ab 100644 --- a/compiler/back_end/experimental/rust/testcode/next_keyword_test.rs +++ b/compiler/back_end/experimental/rust/testcode/next_keyword_test.rs @@ -1,5 +1,5 @@ use emboss_runtime::Error; -use next_keyword_emb_rs::*; +use testdata_next_keyword_emb::*; #[cfg(test)] mod tests { diff --git a/testdata/BUILD b/testdata/BUILD index 8fd1907a..5bd4b1e1 100644 --- a/testdata/BUILD +++ b/testdata/BUILD @@ -382,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"], +) From dacdb4581a5b0ab5a8b5d54bc4dc990cd97039c3 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 11/19] rust: Skip unsupported types in arrays --- .../experimental/rust/emboss_codegen_rust.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index eb098bef..6f896e2f 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -115,6 +115,32 @@ def generate_code(ir: ir_data.EmbossIr) -> tuple[Source, Diagnostics]: return rust_source, diagnostics +def _is_type_omitted(type_ir) -> bool: + if type_ir.has_field("structure") and type_ir.addressable_unit != 8: + return True + 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 = [] @@ -457,6 +483,19 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: ) continue + target_type = _resolve_type(base_type.atomic_type.reference, ir) + if target_type and _is_type_omitted(target_type): + diagnostics.append( + [ + error.warn( + module.source_file_name, + field.source_location, + f"Target type '{target_type.name.name.text}' was omitted because it is a bit-level struct, which is unsupported. Field '{field_name}' will be omitted.", + ) + ] + ) + continue + if not field.has_field("location"): diagnostics.append( [ From 6744475945e018a3194b6bcc05f388782d8b61b3 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 12/19] rust: Add bits support --- .../experimental/rust/emboss_codegen_rust.py | 89 +++++++++--------- .../rust/generated_code_templates | 28 ++++++ runtime/experimental/rust/src/lib.rs | 92 +++++++++++++++++++ 3 files changed, 163 insertions(+), 46 deletions(-) diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index 6f896e2f..a50e1405 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -116,8 +116,6 @@ def generate_code(ir: ir_data.EmbossIr) -> tuple[Source, Diagnostics]: def _is_type_omitted(type_ir) -> bool: - if type_ir.has_field("structure") and type_ir.addressable_unit != 8: - return True return False def _resolve_type(reference, ir): @@ -189,17 +187,7 @@ def _generate_type(type_ir, ir, module, templates, diagnostics) -> str: ] ) - if type_ir.addressable_unit != 8: - diagnostics.append( - [ - error.warn( - module.source_file_name, - type_ir.source_location, - f"Bit-level structs are not yet supported in this backend. Type '{type_ir.name.name.text}' will be omitted.", - ) - ] - ) - return "" + if not type_ir.has_field("structure"): diagnostics.append( @@ -484,17 +472,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: continue target_type = _resolve_type(base_type.atomic_type.reference, ir) - if target_type and _is_type_omitted(target_type): - diagnostics.append( - [ - error.warn( - module.source_file_name, - field.source_location, - f"Target type '{target_type.name.name.text}' was omitted because it is a bit-level struct, which is unsupported. Field '{field_name}' will be omitted.", - ) - ] - ) - continue + if not field.has_field("location"): diagnostics.append( @@ -599,28 +577,47 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: if field.type.has_field("size_in_bits"): bits = ir_util.constant_value(field.type.size_in_bits) - 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), - ) - ) + accessor_template = templates.external_field_accessor + mut_accessor_template = templates.external_mut_field_accessor + + if type_ir.addressable_unit == 1: + bit_offset_str = str(ir_util.constant_value(field.location.start)) + 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_length), + ) + ) + # We don't have bit_external_mut_field_accessor implemented cleanly yet, + # but in templates it is mapped to BitUIntMut which we can define identically. + 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( diff --git a/compiler/back_end/experimental/rust/generated_code_templates b/compiler/back_end/experimental/rust/generated_code_templates index 6761c47f..3034cfad 100644 --- a/compiler/back_end/experimental/rust/generated_code_templates +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -46,6 +46,34 @@ impl ${struct_name}Mut { ${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}; // 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}; + 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> { diff --git a/runtime/experimental/rust/src/lib.rs b/runtime/experimental/rust/src/lib.rs index 9ddd8c0b..3a287c44 100644 --- a/runtime/experimental/rust/src/lib.rs +++ b/runtime/experimental/rust/src/lib.rs @@ -184,6 +184,7 @@ impl_decode_uint!(u64); pub trait SmallestUInt { type T; + fn from_u64(val: u64) -> Self::T; } pub struct SizeSelector; @@ -192,6 +193,8 @@ macro_rules! impl_smallest_uint { $( impl SmallestUInt for SizeSelector<$bits> { type T = $type; + #[inline] + fn from_u64(val: u64) -> Self::T { val as $type } } )+ }; @@ -450,3 +453,92 @@ where 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() + } +} + From 1c7ecd849ccb0df78417ed09dff3d5da35ad3c8e Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 13/19] rust: Nested --- .../experimental/rust/emboss_codegen_rust.py | 77 ++++++++++--------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index a50e1405..a8a164e6 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -142,19 +142,18 @@ def _resolve_type(reference, ir): 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.has_field("enumeration"): - return _generate_enum(type_ir, ir, module, templates, diagnostics) - 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_ir.name.name.text}' will be omitted.", + f"Parameterized types are not yet supported in this backend. Parameters for '{type_name}' will be omitted.", ) ] ) @@ -177,36 +176,30 @@ def _generate_type(type_ir, ir, module, templates, diagnostics) -> str: ) if type_ir.subtype: - diagnostics.append( - [ - error.warn( - module.source_file_name, - type_ir.subtype[0].source_location, - f"Subtypes are not yet supported in this backend. All subtypes in '{type_ir.name.name.text}' will be omitted.", - ) - ] - ) - - + for st in type_ir.subtype: + definitions.append( + _generate_type(st, ir, module, templates, diagnostics) + ) - if not type_ir.has_field("structure"): + 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 types are not yet supported in this backend. Type '{type_ir.name.name.text}' will be omitted.", + f"Non-structure/enum types are not yet supported in this backend. Type '{type_name}' will be omitted.", ) ] ) - return "".join(definitions) - definitions.append(_generate_struct(type_ir, ir, module, templates, diagnostics)) return "".join(definitions) -def _generate_enum(type_ir, ir, module, templates, diagnostics) -> str: - enum_name = type_ir.name.name.text +def _generate_enum(type_ir, ir, module, templates, diagnostics, enum_name) -> str: enum_variants = [] enum_match_variants = [] enum_aliases = [] @@ -340,15 +333,13 @@ def _generate_array_field( 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"): - struct_clean_name = source_name.replace(".", "::") - element_type = code_template.format_template(templates.array_element_type_structure, struct_clean_name=struct_clean_name, storage_type=storage_type) - element_type_mut = code_template.format_template(templates.array_element_type_structure, struct_clean_name=struct_clean_name + "Mut", storage_type=storage_type_mut) - element_constructor = code_template.format_template(templates.array_element_constructor_structure, struct_clean_name=struct_clean_name) - element_constructor_mut = code_template.format_template(templates.array_element_constructor_structure, struct_clean_name=struct_clean_name + "Mut") + 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"): - enum_clean_name = source_name.replace(".", "::") - element_type = code_template.format_template(templates.array_element_type_enumeration, enum_clean_name=enum_clean_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=enum_clean_name, bits=str(bits), byte_order=byte_order, storage_type=storage_type_mut) + 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: @@ -387,8 +378,7 @@ def _generate_array_field( ) -def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: - struct_name = type_ir.name.name.text +def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) -> str: field_accessors = [] mut_field_accessors = [] generated_nested_types = [] @@ -457,15 +447,26 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: ) continue - source_name = "::".join(part.text for part in base_type.atomic_type.reference.source_name) + 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 in _UNSUPPORTED_PRELUDE_TYPES: + if source_name_for_prelude in _UNSUPPORTED_PRELUDE_TYPES: diagnostics.append( [ error.warn( module.source_file_name, field.source_location, - f"Type '{source_name}' is not yet supported in this backend. Field '{field_name}' will be omitted.", + f"Type '{source_name_for_prelude}' is not yet supported in this backend. Field '{field_name}' will be omitted.", ) ] ) @@ -623,7 +624,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: code_template.format_template( templates.struct_field_accessor, field_name=field_name, - type_name=source_name.replace(".", "::"), + type_name=source_name, byte_offset=str(byte_offset), byte_length=str(byte_length), ) @@ -632,7 +633,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: code_template.format_template( templates.struct_mut_field_accessor, field_name=field_name, - type_name=source_name.replace(".", "::"), + type_name=source_name, byte_offset=str(byte_offset), byte_length=str(byte_length), ) @@ -645,7 +646,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: code_template.format_template( templates.enum_field_accessor, field_name=field_name, - enum_name=source_name.replace(".", "::"), + enum_name=source_name, bits=str(bits), byte_order=byte_order, byte_offset=str(byte_offset), @@ -656,7 +657,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics) -> str: code_template.format_template( templates.enum_mut_field_accessor, field_name=field_name, - enum_name=source_name.replace(".", "::"), + enum_name=source_name, bits=str(bits), byte_order=byte_order, byte_offset=str(byte_offset), From 451640388554e8a6b3561bfc8a1b555650c13e31 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 14/19] rust: Skip text attr --- compiler/back_end/experimental/rust/emboss_codegen_rust.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index a8a164e6..b5bc555a 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -531,11 +531,10 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - error.warn( module.source_file_name, unhandled_attr.source_location, - f"Attribute '{unhandled_attr.name.text}' is not yet supported in this backend. Field '{field_name}' will be omitted.", + f"Attribute '{unhandled_attr.name.text}' is not yet supported in this backend. It will be ignored for field '{field_name}'.", ) ] ) - continue byte_order_attr = ir_util.get_attribute(field.attribute, "byte_order") if byte_order_attr: From a58b83c57e571696965b961ba260e12ef6aaf0ff Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 15/19] rust: Fix field accessors (need to rebase) --- .../experimental/rust/emboss_codegen_rust.py | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index b5bc555a..3056d598 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -577,47 +577,47 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - 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_str = str(ir_util.constant_value(field.location.start)) - 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_length), - ) + accessor_template = templates.external_field_accessor + mut_accessor_template = templates.external_mut_field_accessor + + if type_ir.addressable_unit == 1: + bit_offset_str = str(ir_util.constant_value(field.location.start)) + 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_length), ) - # We don't have bit_external_mut_field_accessor implemented cleanly yet, - # but in templates it is mapped to BitUIntMut which we can define identically. - 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), - ) + ) + # We don't have bit_external_mut_field_accessor implemented cleanly yet, + # but in templates it is mapped to BitUIntMut which we can define identically. + 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), - ) + ) + 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( From 04285f8b45e1225dbd98d25636f8d0fda3ed9fd9 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 16/19] rust: Add support for virtual fields --- .../experimental/rust/emboss_codegen_rust.py | 124 ++++++++++++++---- .../rust/generated_code_templates | 78 +++++++---- runtime/experimental/rust/src/lib.rs | 24 ++++ 3 files changed, 172 insertions(+), 54 deletions(-) diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index 3056d598..30060355 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -254,6 +254,20 @@ def _generate_enum(type_ir, ir, module, templates, diagnostics, enum_name) -> st ) +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)) @@ -262,12 +276,12 @@ def _generate_expression(expr, ir, module, generated_fields, templates, self_ref return "true" if expr.boolean_constant.value else "false" if expr.has_field("field_reference"): - if len(expr.field_reference.path) > 1: + 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 - ref_name = expr.field_reference.path[0].canonical_name.object_path[-1] - if ref_name not in generated_fields: - return None - return code_template.format_template(templates.expr_field_reference, self_ref=self_ref, field_name=ref_name) + + 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 @@ -286,11 +300,13 @@ def _generate_expression(expr, ir, module, generated_fields, templates, self_ref 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.MINIMUM.value: - return code_template.format_template(templates.expr_minimum, left=args[0], right=args[1]) if func == ir_data.FunctionMapping.CHOICE.value: - return code_template.format_template(templates.expr_choice, cond=args[0], true_branch=args[1], false_branch=args[2]) - + 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 @@ -398,7 +414,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - if field_name.startswith("$"): continue - if field.has_field("existence_condition"): + 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( @@ -412,6 +428,58 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - ) 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 + + 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. + pass + else: + field_accessors.append(code_template.format_template( + templates.virtual_field_accessor, + field_name=field_name, + return_type=return_type, + expression=expr_str, + )) + mut_field_accessors.append(code_template.format_template( + templates.mut_virtual_field_accessor, + 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 @@ -474,19 +542,6 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - target_type = _resolve_type(base_type.atomic_type.reference, ir) - - if not field.has_field("location"): - diagnostics.append( - [ - error.warn( - module.source_file_name, - field.source_location, - f"Virtual fields are not yet supported in this backend. Field '{field_name}' will be omitted.", - ) - ] - ) - continue - 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) @@ -573,7 +628,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - ) else: if referenced_type.has_field("external"): - bits = const_byte_length * 8 + 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) @@ -581,7 +636,11 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - mut_accessor_template = templates.external_mut_field_accessor if type_ir.addressable_unit == 1: - bit_offset_str = str(ir_util.constant_value(field.location.start)) + 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, @@ -590,11 +649,20 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - bits=str(bits), byte_order=byte_order, bit_offset=bit_offset_str, - byte_length=str(byte_length), + 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), ) ) - # We don't have bit_external_mut_field_accessor implemented cleanly yet, - # but in templates it is mapped to BitUIntMut which we can define identically. else: field_accessors.append( code_template.format_template( diff --git a/compiler/back_end/experimental/rust/generated_code_templates b/compiler/back_end/experimental/rust/generated_code_templates index 3034cfad..9ebf5f67 100644 --- a/compiler/back_end/experimental/rust/generated_code_templates +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -17,7 +17,7 @@ #![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}; +pub use emboss_runtime::{Error, Storage, MutStorage, TryRead, TryWrite, UInt, Int, VirtualField}; ${imports_list} @@ -50,7 +50,7 @@ ${mut_field_accessors} 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}; // Total struct size in bytes + let len = (${byte_length}) as usize; // Total struct size in bytes core::result::Result::Ok((offset, len)) })(); let storage = match offset_res { @@ -64,7 +64,7 @@ ${mut_field_accessors} 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}; + let len = (${byte_length}) as usize; core::result::Result::Ok((offset, len)) })(); let storage = match offset_res { @@ -77,8 +77,8 @@ ${mut_field_accessors} // ** 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}; - let len = ${byte_length}; + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; core::result::Result::Ok((offset, len)) })(); let storage = match offset_res { @@ -91,8 +91,8 @@ ${mut_field_accessors} // ** 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}; - let len = ${byte_length}; + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; core::result::Result::Ok((offset, len)) })(); let storage = match offset_res { @@ -105,8 +105,8 @@ ${mut_field_accessors} // ** 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}; - let len = ${byte_length}; + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; core::result::Result::Ok((offset, len)) })(); let storage = match offset_res { @@ -119,8 +119,8 @@ ${mut_field_accessors} // ** 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}; - let len = ${byte_length}; + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; core::result::Result::Ok((offset, len)) })(); let storage = match offset_res { @@ -170,13 +170,13 @@ ${enum_aliases} ${variant_value} => Ok(${enum_name}::${variant_name}), // ** expr_addition ** // -(${left} + ${right}) +( (${left}) as i64 + (${right}) as i64 ) // ** expr_subtraction ** // -(${left} - ${right}) +( (${left}) as i64 - (${right}) as i64 ) // ** expr_multiplication ** // -(${left} * ${right}) +( (${left}) as i64 * (${right}) as i64 ) // ** expr_constant ** // ${constant_value} @@ -185,10 +185,10 @@ ${constant_value} { let val = ${self_ref}.${field_name}().try_read()?; val as usize } // ** expr_maximum ** // -core::cmp::max(${left}, ${right}) +core::cmp::max((${left}) as i64, (${right}) as i64) // ** expr_minimum ** // -core::cmp::min(${left}, ${right}) +core::cmp::min((${left}) as i64, (${right}) as i64) // ** expr_choice ** // (if ${condition} { ${true_value} } else { ${false_value} }) @@ -197,8 +197,8 @@ core::cmp::min(${left}, ${right}) // ** 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}; - let len = ${byte_length}; + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; core::result::Result::Ok((offset, len)) })(); let storage = match offset_res { @@ -211,8 +211,8 @@ core::cmp::min(${left}, ${right}) // ** 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}; - let len = ${byte_length}; + let offset = (${byte_offset}) as usize; + let len = (${byte_length}) as usize; core::result::Result::Ok((offset, len)) })(); let storage = match offset_res { @@ -225,9 +225,9 @@ core::cmp::min(${left}, ${right}) // ** 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}; - let len = ${byte_length}; - let element_count = len / (${element_size}); + 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 { @@ -242,9 +242,9 @@ core::cmp::min(${left}, ${right}) // ** 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}; - let len = ${byte_length}; - let element_count = len / (${element_size}); + 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 { @@ -328,3 +328,29 @@ emboss_runtime::EnumViewMut<${enum_clean_name}, ${bits}, emboss_runtime::${byte_ emboss_runtime::EnumViewMut::new(emboss_runtime::UInt::new(storage)) + +// ** expr_field_reference_no_cast ** // +{ let val = ${self_ref}${path_expr}.try_read()?; 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) + } + diff --git a/runtime/experimental/rust/src/lib.rs b/runtime/experimental/rust/src/lib.rs index 3a287c44..d2933650 100644 --- a/runtime/experimental/rust/src/lib.rs +++ b/runtime/experimental/rust/src/lib.rs @@ -542,3 +542,27 @@ where } } +#[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 + } +} + From f05005d6fc9c3cb7fd2ba84f404381a247dd6f86 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 17/19] rust: Fix enum in bits --- .../experimental/rust/emboss_codegen_rust.py | 69 +++++++++++++------ .../rust/generated_code_templates | 32 ++++++++- 2 files changed, 78 insertions(+), 23 deletions(-) diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index 30060355..88b2aea3 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -706,31 +706,58 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - ) ) elif referenced_type.has_field("enumeration"): - bits = const_byte_length * 8 + 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) - 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), + 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.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), + 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( [ diff --git a/compiler/back_end/experimental/rust/generated_code_templates b/compiler/back_end/experimental/rust/generated_code_templates index 9ebf5f67..b794ba29 100644 --- a/compiler/back_end/experimental/rust/generated_code_templates +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -182,7 +182,7 @@ ${enum_aliases} ${constant_value} // ** expr_field_reference ** // -{ let val = ${self_ref}.${field_name}().try_read()?; val as usize } +{ 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) @@ -330,7 +330,7 @@ emboss_runtime::EnumViewMut::new(emboss_runtime::UInt::new(storage)) // ** expr_field_reference_no_cast ** // -{ let val = ${self_ref}${path_expr}.try_read()?; val } +({ 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}) @@ -354,3 +354,31 @@ emboss_runtime::EnumViewMut::new(emboss_runtime::UInt::new(storage)) 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)) + } + From 3e72f4c80ff4613da0990b5ed0cccc48f5e15794 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Tue, 14 Jul 2026 15:00:00 +0000 Subject: [PATCH 18/19] rust: Flag partial support --- .../experimental/rust/emboss_codegen_rust.py | 66 ++++++++++++++----- .../rust/generated_code_templates | 16 +++++ 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index 88b2aea3..5525eb91 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -32,7 +32,7 @@ ErrorList = list[list[error._Message]] _TEMPLATE_FILE_NAME = "generated_code_templates" -_UNSUPPORTED_PRELUDE_TYPES = {"Bcd", "Flag", "Float"} +_UNSUPPORTED_PRELUDE_TYPES = {"Bcd", "Float"} _SYNTHETIC_ATTRIBUTES = {"expected_back_ends", "fixed_size_in_bits"} @@ -445,6 +445,20 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - ) 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( @@ -462,20 +476,41 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - 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. - pass + # 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: - field_accessors.append(code_template.format_template( - templates.virtual_field_accessor, - field_name=field_name, - return_type=return_type, - expression=expr_str, - )) - mut_field_accessors.append(code_template.format_template( - templates.mut_virtual_field_accessor, - field_name=field_name, - return_type=return_type, - expression=expr_str, - )) + 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 @@ -527,7 +562,8 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - 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( [ diff --git a/compiler/back_end/experimental/rust/generated_code_templates b/compiler/back_end/experimental/rust/generated_code_templates index b794ba29..3346a2cc 100644 --- a/compiler/back_end/experimental/rust/generated_code_templates +++ b/compiler/back_end/experimental/rust/generated_code_templates @@ -354,6 +354,22 @@ emboss_runtime::EnumViewMut::new(emboss_runtime::UInt::new(storage)) 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> { From 82bcda8647de94fa7562e13ff863d31a72bbbdd6 Mon Sep 17 00:00:00 2001 From: Jason Graffius Date: Thu, 16 Jul 2026 19:10:23 +0000 Subject: [PATCH 19/19] rust: Fix format and copyright headers --- MODULE.bazel | 14 ++ compiler/back_end/experimental/rust/BUILD | 14 ++ .../experimental/rust/emboss_codegen_rust.py | 215 +++++++++++++----- .../back_end/experimental/rust/testcode/BUILD | 14 ++ .../rust/testcode/auto_array_size_test.rs | 14 ++ .../rust/testcode/dynamic_size_test.rs | 14 ++ .../experimental/rust/testcode/enum_test.rs | 14 ++ .../rust/testcode/imported_test.rs | 14 ++ .../rust/testcode/importer_test.rs | 14 ++ .../rust/testcode/nested_structure_test.rs | 14 ++ .../rust/testcode/next_keyword_test.rs | 14 ++ runtime/experimental/rust/BUILD | 14 ++ runtime/experimental/rust/Cargo.toml | 14 ++ 13 files changed, 323 insertions(+), 60 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 1b0e505c..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", diff --git a/compiler/back_end/experimental/rust/BUILD b/compiler/back_end/experimental/rust/BUILD index fbbf14b3..fa561432 100644 --- a/compiler/back_end/experimental/rust/BUILD +++ b/compiler/back_end/experimental/rust/BUILD @@ -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. + load("@rules_python//python:py_binary.bzl", "py_binary") package( diff --git a/compiler/back_end/experimental/rust/emboss_codegen_rust.py b/compiler/back_end/experimental/rust/emboss_codegen_rust.py index 5525eb91..89a4ec71 100644 --- a/compiler/back_end/experimental/rust/emboss_codegen_rust.py +++ b/compiler/back_end/experimental/rust/emboss_codegen_rust.py @@ -76,10 +76,8 @@ def generate_code(ir: ir_data.EmbossIr) -> tuple[Source, Diagnostics]: imports_list = [] struct_definitions = [] module = ir.module[0] - - real_imports = [ - imp for imp in module.foreign_import if imp.file_name.text != "" - ] + + 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 @@ -118,10 +116,11 @@ def generate_code(ir: ir_data.EmbossIr) -> tuple[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: @@ -139,6 +138,7 @@ def _resolve_type(reference, ir): return current return None + def _generate_type(type_ir, ir, module, templates, diagnostics) -> str: definitions = [] @@ -177,14 +177,16 @@ def _generate_type(type_ir, ir, module, templates, diagnostics) -> str: if type_ir.subtype: for st in type_ir.subtype: - definitions.append( - _generate_type(st, ir, module, templates, diagnostics) - ) + 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)) + 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)) + definitions.append( + _generate_enum(type_ir, ir, module, templates, diagnostics, type_name) + ) else: diagnostics.append( [ @@ -266,47 +268,72 @@ def _rust_type_for_expr_type(expr_type): return "u64" - - -def _generate_expression(expr, ir, module, generated_fields, templates, self_ref="self"): +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] + 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) + 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) + 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]) + 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]) + 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]) + 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]) + 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]) + 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]) + 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 code_template.format_template( + templates.expr_less, left=args[0], right=args[1] + ) + return None return None @@ -339,25 +366,70 @@ def _generate_array_field( 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) + 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") + 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) + 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 @@ -414,7 +486,9 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - if field_name.startswith("$"): continue - if field.has_field("existence_condition") and not field.has_field("read_transform"): + 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( @@ -444,9 +518,14 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - ] ) 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 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( [ @@ -459,7 +538,9 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - ) continue - expr_str = _generate_expression(field.read_transform, ir, module, generated_fields, templates) + expr_str = _generate_expression( + field.read_transform, ir, module, generated_fields, templates + ) if expr_str is None: diagnostics.append( [ @@ -471,10 +552,12 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - ] ) 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"): + + 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( @@ -494,7 +577,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - else: accessor_template = templates.virtual_field_accessor mut_accessor_template = templates.mut_virtual_field_accessor - + field_accessors.append( code_template.format_template( accessor_template, @@ -511,11 +594,13 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - 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")): + 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") @@ -550,15 +635,21 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - ) 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] + 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 + 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) @@ -578,8 +669,12 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - 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) + 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" @@ -635,7 +730,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - 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: @@ -670,13 +765,13 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - 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, @@ -809,7 +904,7 @@ def _generate_struct(type_ir, ir, module, templates, diagnostics, struct_name) - ] ) continue - + generated_fields.add(field_name) main_struct_def = code_template.format_template( diff --git a/compiler/back_end/experimental/rust/testcode/BUILD b/compiler/back_end/experimental/rust/testcode/BUILD index f7f081a5..6ae2fe90 100644 --- a/compiler/back_end/experimental/rust/testcode/BUILD +++ b/compiler/back_end/experimental/rust/testcode/BUILD @@ -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. + load("@rules_rust//rust:defs.bzl", "rust_test") load("//:build_defs.bzl", "emboss_rust_library") rust_test( 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 index 4e5d02de..8de84312 100644 --- a/compiler/back_end/experimental/rust/testcode/auto_array_size_test.rs +++ b/compiler/back_end/experimental/rust/testcode/auto_array_size_test.rs @@ -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. + use testdata_auto_array_size_emb::*; #[test] diff --git a/compiler/back_end/experimental/rust/testcode/dynamic_size_test.rs b/compiler/back_end/experimental/rust/testcode/dynamic_size_test.rs index 87c97931..7ed09191 100644 --- a/compiler/back_end/experimental/rust/testcode/dynamic_size_test.rs +++ b/compiler/back_end/experimental/rust/testcode/dynamic_size_test.rs @@ -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. + pub use testdata_dynamic_size_emb::*; use emboss_runtime::{prelude::*, Error}; diff --git a/compiler/back_end/experimental/rust/testcode/enum_test.rs b/compiler/back_end/experimental/rust/testcode/enum_test.rs index d170c8ed..4cc52548 100644 --- a/compiler/back_end/experimental/rust/testcode/enum_test.rs +++ b/compiler/back_end/experimental/rust/testcode/enum_test.rs @@ -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. + pub use testdata_enum_emb::*; #[test] diff --git a/compiler/back_end/experimental/rust/testcode/imported_test.rs b/compiler/back_end/experimental/rust/testcode/imported_test.rs index 4d7d93c6..7c5edfbb 100644 --- a/compiler/back_end/experimental/rust/testcode/imported_test.rs +++ b/compiler/back_end/experimental/rust/testcode/imported_test.rs @@ -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. + use emboss_runtime::{prelude::*, Error}; use testdata_imported_emb::*; diff --git a/compiler/back_end/experimental/rust/testcode/importer_test.rs b/compiler/back_end/experimental/rust/testcode/importer_test.rs index f72b065c..070c63cf 100644 --- a/compiler/back_end/experimental/rust/testcode/importer_test.rs +++ b/compiler/back_end/experimental/rust/testcode/importer_test.rs @@ -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. + use testdata_importer_emb::{Outer, OuterMut}; #[test] diff --git a/compiler/back_end/experimental/rust/testcode/nested_structure_test.rs b/compiler/back_end/experimental/rust/testcode/nested_structure_test.rs index 9a7bb166..eebe25b7 100644 --- a/compiler/back_end/experimental/rust/testcode/nested_structure_test.rs +++ b/compiler/back_end/experimental/rust/testcode/nested_structure_test.rs @@ -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. + use emboss_runtime::{prelude::*, Error}; #[test] diff --git a/compiler/back_end/experimental/rust/testcode/next_keyword_test.rs b/compiler/back_end/experimental/rust/testcode/next_keyword_test.rs index 6d65c6ab..48064be2 100644 --- a/compiler/back_end/experimental/rust/testcode/next_keyword_test.rs +++ b/compiler/back_end/experimental/rust/testcode/next_keyword_test.rs @@ -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. + use emboss_runtime::Error; use testdata_next_keyword_emb::*; diff --git a/runtime/experimental/rust/BUILD b/runtime/experimental/rust/BUILD index cfa2c36e..6dbb3c9c 100644 --- a/runtime/experimental/rust/BUILD +++ b/runtime/experimental/rust/BUILD @@ -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. + load("@rules_rust//rust:defs.bzl", "rust_library") package( diff --git a/runtime/experimental/rust/Cargo.toml b/runtime/experimental/rust/Cargo.toml index ba72c636..9c0f9ffb 100644 --- a/runtime/experimental/rust/Cargo.toml +++ b/runtime/experimental/rust/Cargo.toml @@ -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. + [package] name = "emboss_runtime" version = "0.1.0"