diff options
| author | Eugen Wissner <belka@caraus.de> | 2026-09-14 21:11:03 +0200 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2026-09-14 21:11:03 +0200 |
| commit | 52c8ca2c06da93c8d14e80a256e8996c1fa1e885 (patch) | |
| tree | 3579cffe682bb2d2270e5d892f26ae3c197880e6 | |
| parent | 49e6a0518eb6b7b09938b562ef270028337508bf (diff) | |
| download | elna-52c8ca2c06da93c8d14e80a256e8996c1fa1e885.tar.gz | |
Implement volatile operations
| -rw-r--r-- | boot/symbol.cc | 18 | ||||
| -rw-r--r-- | boot/type_check.cc | 55 | ||||
| -rw-r--r-- | gcc/gcc/elna-generic.cc | 50 | ||||
| -rw-r--r-- | include/elna/boot/type_check.h | 26 | ||||
| -rw-r--r-- | include/elna/gcc/elna-generic.h | 4 |
5 files changed, 143 insertions, 10 deletions
diff --git a/boot/symbol.cc b/boot/symbol.cc index 466715b..55f91ea 100644 --- a/boot/symbol.cc +++ b/boot/symbol.cc @@ -401,6 +401,24 @@ namespace elna::boot std::vector<std::string>{ "condition" }); result->enter("assert", assert_info); + const type load_parameter = type(std::make_shared<parameter_type>("T")); + procedure_type volatile_load_symbol{ procedure_type::return_t(load_parameter) }; + volatile_load_symbol.parameters.emplace_back(std::make_shared<pointer_type>(load_parameter)); + std::shared_ptr<procedure_info> const volatile_load_info = + std::make_shared<procedure_info>(volatile_load_symbol, std::vector<std::string>{ "source" }); + volatile_load_info->parameters.push_back(load_parameter); + result->enter("volatile_load", volatile_load_info); + + const type store_parameter = type(std::make_shared<parameter_type>("T")); + procedure_type volatile_store_symbol{ procedure_type::return_t() }; + volatile_store_symbol.parameters.emplace_back(std::make_shared<pointer_type>(store_parameter)); + volatile_store_symbol.parameters.push_back(store_parameter); + std::shared_ptr<procedure_info> const volatile_store_info = + std::make_shared<procedure_info>(volatile_store_symbol, + std::vector<std::string>{ "target", "value" }); + volatile_store_info->parameters.push_back(store_parameter); + result->enter("volatile_store", volatile_store_info); + return result; } diff --git a/boot/type_check.cc b/boot/type_check.cc index eee5b42..2b82bec 100644 --- a/boot/type_check.cc +++ b/boot/type_check.cc @@ -142,6 +142,11 @@ namespace elna::boot return "Type '" + payload.actual.to_string() + "' cannot be a type argument of '" + this->generic_name + "', it is not a pointer type"; } + else if constexpr (std::is_same_v<T, argument_not_scalar>) + { + return "Type '" + payload.actual.to_string() + "' cannot be a type argument of '" + + this->generic_name + "', it is not a scalar type"; + } else if constexpr (std::is_same_v<T, constant_argument>) { return "Constant type '" + payload.actual.to_string() @@ -1217,23 +1222,50 @@ namespace elna::boot return true; } + /* + * The volatile builtins take the pointee as their type argument, so the + * rule applies to what is loaded or stored rather than to a pointer. + */ + static argument_rule builtin_argument_rule(const std::string& name) + { + if (name == "volatile_load") + { + return argument_rule::scalar; + } + if (name == "volatile_store") + { + return argument_rule::mutable_scalar; + } + return argument_rule::pointer; + } + void type_analysis_visitor::check_arguments(const named_expression& reference, - const std::vector<type>& arguments) + const std::vector<type>& arguments, const argument_rule rule) { for (const type& argument : arguments) { - const type resolved = resolve_aliases(argument); + if (argument.empty()) + { + continue; + } + const type underlying = resolve_underlying_type(argument); - if (resolved.get<constant_type>() != nullptr) + if (rule != argument_rule::scalar + && resolve_aliases(argument).get<constant_type>() != nullptr) { add_error<generic_error>(reference.position(), reference.name, generic_error::constant_argument{ argument }); } - else if (!argument.empty() && !is_any_pointer_type(resolve_underlying_type(argument))) + else if (rule == argument_rule::pointer && !is_any_pointer_type(underlying)) { add_error<generic_error>(reference.position(), reference.name, generic_error::argument_not_pointer{ argument }); } + else if (rule != argument_rule::pointer && !is_scalar_type(underlying)) + { + add_error<generic_error>(reference.position(), reference.name, + generic_error::argument_not_scalar{ argument }); + } } } @@ -1261,7 +1293,8 @@ namespace elna::boot } else if (!expression->arguments.empty()) { - check_arguments(*expression, expression->argument_types); + check_arguments(*expression, expression->argument_types, + builtin_argument_rule(expression->name)); } return; } @@ -1284,7 +1317,7 @@ namespace elna::boot } else { - check_arguments(*expression, expression->argument_types); + check_arguments(*expression, expression->argument_types, argument_rule::pointer); } } else if (std::shared_ptr<generic_type> const generic = unwrap_generic(denoted)) @@ -1335,7 +1368,7 @@ namespace elna::boot return type(); } } - check_arguments(reference, bindings); + check_arguments(reference, bindings, builtin_argument_rule(reference.name)); return substitute(type(std::make_shared<procedure_type>(procedure.symbol)), procedure.parameters, bindings); @@ -1396,6 +1429,14 @@ namespace elna::boot } inferred = procedure; callable_type = infer_call(*call, *reference, *procedure); + + // Name analysis decorated the call with the declared return + // type, which still mentions the parameters inference has + // just bound. + if (auto substituted = callable_type.get<procedure_type>()) + { + call->type_decoration = substituted->return_type.proper_type; + } } } if (auto procedure = callable_type.get<procedure_type>()) diff --git a/gcc/gcc/elna-generic.cc b/gcc/gcc/elna-generic.cc index ed75fa4..3b43f5e 100644 --- a/gcc/gcc/elna-generic.cc +++ b/gcc/gcc/elna-generic.cc @@ -92,6 +92,46 @@ namespace elna::gcc } } + // Volatile-qualifying the pointee is what carries the guarantee into + // GIMPLE; the flags on the reference itself mirror what the C front end + // sets when it dereferences a volatile pointer. + tree generic_visitor::build_volatile_reference(location_t location, + boot::expression& pointer, const boot::type& accessed) + { + pointer.accept(this); + + tree pointee = get_inner_alias(accessed, this->symbols); + tree qualified = build_qualified_type(pointee, TYPE_QUALS(pointee) | TYPE_QUAL_VOLATILE); + tree address = fold_convert_loc(location, build_pointer_type(qualified), + prepare_rvalue(this->current_expression)); + tree reference = build_simple_mem_ref_loc(location, address); + + TREE_THIS_VOLATILE(reference) = 1; + TREE_SIDE_EFFECTS(reference) = 1; + + return reference; + } + + void generic_visitor::build_volatile_load_builtin(location_t call_location, + boot::procedure_call *call) + { + this->current_expression = build_volatile_reference(call_location, + *call->arguments.at(0), call->type_decoration); + } + + void generic_visitor::build_volatile_store_builtin(location_t call_location, + boot::procedure_call *call) + { + tree target = build_volatile_reference(call_location, + *call->arguments.at(0), call->argument_types.at(1)); + + call->arguments.at(1)->accept(this); + tree value = prepare_rvalue(this->current_expression); + + this->current_expression = build2_loc(call_location, MODIFY_EXPR, void_type_node, + target, value); + } + bool generic_visitor::build_builtin_procedures(boot::procedure_call *call) { const location_t call_location = get_location(&call->position()); @@ -103,6 +143,16 @@ namespace elna::gcc build_assert_builtin(call_location, call->arguments); return true; } + if (named_call->name == "volatile_load") + { + build_volatile_load_builtin(call_location, call); + return true; + } + if (named_call->name == "volatile_store") + { + build_volatile_store_builtin(call_location, call); + return true; + } } return false; } diff --git a/include/elna/boot/type_check.h b/include/elna/boot/type_check.h index 5faec43..582b887 100644 --- a/include/elna/boot/type_check.h +++ b/include/elna/boot/type_check.h @@ -181,6 +181,10 @@ namespace elna::boot { type actual; }; + struct argument_not_scalar + { + type actual; + }; struct constant_argument { type actual; @@ -196,8 +200,8 @@ namespace elna::boot }; using payload_type = std::variant<not_generic, unused_parameter, - argument_not_pointer, constant_argument, uninferrable_parameter, - shape_mismatch>; + argument_not_pointer, argument_not_scalar, constant_argument, + uninferrable_parameter, shape_mismatch>; generic_error(const source_position position, const std::string& generic_name, payload_type payload); @@ -210,6 +214,21 @@ namespace elna::boot }; /** + * What a generic declaration accepts as a type argument. + * + * An erased generic takes a pointer, because that is what its single + * compiled body knows how to move. A builtin has no body to erase, so it + * takes whatever it can load or store, and only a reading one tolerates a + * constant. + */ + enum class argument_rule + { + pointer, + scalar, + mutable_scalar + }; + + /** * Chain of responsibility for type compatibility checks. * * Populate \c ctx with pre-resolved types, then call \c run(). @@ -265,7 +284,8 @@ namespace elna::boot static bool is_equality_compatible(const type& left, const type& right); void visit_and_validate_condition(expression& condition); - void check_arguments(const named_expression& reference, const std::vector<type>& arguments); + void check_arguments(const named_expression& reference, const std::vector<type>& arguments, + const argument_rule rule); void check_parameters_used(const source_position position, const std::string& name, const std::vector<type>& parameters, const type& heading); type infer_call(procedure_call& call, const named_expression& reference, diff --git a/include/elna/gcc/elna-generic.h b/include/elna/gcc/elna-generic.h index fbd68d8..dde883a 100644 --- a/include/elna/gcc/elna-generic.h +++ b/include/elna/gcc/elna-generic.h @@ -61,6 +61,10 @@ namespace elna::gcc const std::vector<boot::type>& argument_types); bool build_builtin_procedures(boot::procedure_call *call); void build_assert_builtin(location_t call_location, const std::vector<boot::expression *>& arguments); + tree build_volatile_reference(location_t location, boot::expression& pointer, + const boot::type& accessed); + void build_volatile_load_builtin(location_t call_location, boot::procedure_call *call); + void build_volatile_store_builtin(location_t call_location, boot::procedure_call *call); void visit_statements(const std::vector<boot::statement *>& statements); void assert_constant(); |
