aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2026-09-14 21:11:03 +0200
committerEugen Wissner <belka@caraus.de>2026-09-14 21:11:03 +0200
commit52c8ca2c06da93c8d14e80a256e8996c1fa1e885 (patch)
tree3579cffe682bb2d2270e5d892f26ae3c197880e6
parent49e6a0518eb6b7b09938b562ef270028337508bf (diff)
downloadelna-52c8ca2c06da93c8d14e80a256e8996c1fa1e885.tar.gz
Implement volatile operations
-rw-r--r--boot/symbol.cc18
-rw-r--r--boot/type_check.cc55
-rw-r--r--gcc/gcc/elna-generic.cc50
-rw-r--r--include/elna/boot/type_check.h26
-rw-r--r--include/elna/gcc/elna-generic.h4
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();