Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions compiler/rustc_abi/src/layout/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ pub trait TyAbiInterface<'a, C>: Sized + std::fmt::Debug {
fn is_tuple(this: TyAndLayout<'a, Self>) -> bool;
fn is_unit(this: TyAndLayout<'a, Self>) -> bool;
fn is_transparent(this: TyAndLayout<'a, Self>) -> bool;
/// See [`TyAndLayout::pass_indirectly_in_non_rustic_abis`] for details.
fn is_pass_indirectly_in_non_rustic_abis_flag_set(this: TyAndLayout<'a, Self>) -> bool;
}

impl<'a, Ty> TyAndLayout<'a, Ty> {
Expand Down Expand Up @@ -269,6 +271,34 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {
Ty::is_transparent(self)
}

/// If this method returns `true`, then this type should always have a `PassMode` of
/// `Indirect { on_stack: false, .. }` when being used as the argument type of a function with a
/// non-Rustic ABI (this is true for structs annotated with the
/// `#[rustc_pass_indirectly_in_non_rustic_abis]` attribute).
///
/// This is used to replicate some of the behaviour of C array-to-pointer decay; however unlike
/// C any changes the caller makes to the passed value will not be reflected in the callee, so
/// the attribute is only useful for types where observing the value in the caller after the
/// function call isn't allowed (a.k.a. `va_list`).
///
/// This function handles transparent types automatically.
pub fn pass_indirectly_in_non_rustic_abis<C>(mut self, cx: &C) -> bool
where
Ty: TyAbiInterface<'a, C> + Copy,
{
loop {
if Ty::is_pass_indirectly_in_non_rustic_abis_flag_set(self) {
return true;
} else if self.is_transparent()
&& let Some((_, field)) = self.non_1zst_field(cx)
{
self = field;
} else {
return false;
}
}
}

/// Finds the one field that is not a 1-ZST.
/// Returns `None` if there are multiple non-1-ZST fields or only 1-ZST-fields.
pub fn non_1zst_field<C>(&self, cx: &C) -> Option<(FieldIdx, Self)>
Expand Down
15 changes: 9 additions & 6 deletions compiler/rustc_abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,17 @@ bitflags! {
const IS_C = 1 << 0;
const IS_SIMD = 1 << 1;
const IS_TRANSPARENT = 1 << 2;
// Internal only for now. If true, don't reorder fields.
// On its own it does not prevent ABI optimizations.
/// Internal only for now. If true, don't reorder fields.
/// On its own it does not prevent ABI optimizations.
const IS_LINEAR = 1 << 3;
// If true, the type's crate has opted into layout randomization.
// Other flags can still inhibit reordering and thus randomization.
// The seed stored in `ReprOptions.field_shuffle_seed`.
/// If true, the type's crate has opted into layout randomization.
/// Other flags can still inhibit reordering and thus randomization.
/// The seed stored in `ReprOptions.field_shuffle_seed`.
const RANDOMIZE_LAYOUT = 1 << 4;
// Any of these flags being set prevent field reordering optimisation.
/// If true, the type is always passed indirectly by non-Rustic ABIs.
/// See [`TyAndLayout::pass_indirectly_in_non_rustic_abis`] for details.
const PASS_INDIRECTLY_IN_NON_RUSTIC_ABIS = 1 << 5;
/// Any of these flags being set prevent field reordering optimisation.
const FIELD_ORDER_UNOPTIMIZABLE = ReprFlags::IS_C.bits()
| ReprFlags::IS_SIMD.bits()
| ReprFlags::IS_LINEAR.bits();
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,3 +673,12 @@ impl<S: Stage> SingleAttributeParser<S> for SanitizeParser {
Some(AttributeKind::Sanitize { on_set, off_set, span: cx.attr_span })
}
}

pub(crate) struct RustcPassIndirectlyInNonRusticAbisParser;

impl<S: Stage> NoArgsAttributeParser<S> for RustcPassIndirectlyInNonRusticAbisParser {
const PATH: &[Symbol] = &[sym::rustc_pass_indirectly_in_non_rustic_abis];
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcPassIndirectlyInNonRusticAbis;
}
6 changes: 4 additions & 2 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ use crate::attributes::allow_unstable::{
use crate::attributes::body::CoroutineParser;
use crate::attributes::codegen_attrs::{
ColdParser, CoverageParser, ExportNameParser, ForceTargetFeatureParser, NakedParser,
NoMangleParser, ObjcClassParser, ObjcSelectorParser, OptimizeParser, SanitizeParser,
TargetFeatureParser, TrackCallerParser, UsedParser,
NoMangleParser, ObjcClassParser, ObjcSelectorParser, OptimizeParser,
RustcPassIndirectlyInNonRusticAbisParser, SanitizeParser, TargetFeatureParser,
TrackCallerParser, UsedParser,
};
use crate::attributes::confusables::ConfusablesParser;
use crate::attributes::crate_level::{
Expand Down Expand Up @@ -238,6 +239,7 @@ attribute_parsers!(
Single<WithoutArgs<ProcMacroParser>>,
Single<WithoutArgs<PubTransparentParser>>,
Single<WithoutArgs<RustcCoherenceIsCoreParser>>,
Single<WithoutArgs<RustcPassIndirectlyInNonRusticAbisParser>>,
Single<WithoutArgs<SpecializationTraitParser>>,
Single<WithoutArgs<StdInternalSymbolParser>>,
Single<WithoutArgs<TrackCallerParser>>,
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,12 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
template!(Word, "https://doc.rust-lang.org/reference/attributes/codegen.html#the-naked-attribute"),
WarnFollowing, EncodeCrossCrate::No
),
// See `TyAndLayout::pass_indirectly_in_non_rustic_abis` for details.
rustc_attr!(
rustc_pass_indirectly_in_non_rustic_abis, Normal, template!(Word), ErrorFollowing,
EncodeCrossCrate::No,
"types marked with `#[rustc_pass_indirectly_in_non_rustic_abis]` are always passed indirectly by non-Rustic abis."
),

// Limits:
ungated!(
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,9 @@ pub enum AttributeKind {
/// Represents `#[rustc_object_lifetime_default]`.
RustcObjectLifetimeDefault,

/// Represents `#[rustc_pass_indirectly_in_non_rustic_abis]`
RustcPassIndirectlyInNonRusticAbis(Span),

/// Represents `#[rustc_simd_monomorphize_lane_limit = "N"]`.
RustcSimdMonomorphizeLaneLimit(Limit),

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/attrs/encode_cross_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ impl AttributeKind {
RustcLayoutScalarValidRangeEnd(..) => Yes,
RustcLayoutScalarValidRangeStart(..) => Yes,
RustcObjectLifetimeDefault => No,
RustcPassIndirectlyInNonRusticAbis(..) => No,
RustcSimdMonomorphizeLaneLimit(..) => Yes, // Affects layout computation, which needs to work cross-crate
Sanitize { .. } => No,
ShouldPanic { .. } => No,
Expand Down
7 changes: 6 additions & 1 deletion compiler/rustc_middle/src/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{cmp, fmt};

use rustc_abi::{
AddressSpace, Align, ExternAbi, FieldIdx, FieldsShape, HasDataLayout, LayoutData, PointeeInfo,
PointerKind, Primitive, ReprOptions, Scalar, Size, TagEncoding, TargetDataLayout,
PointerKind, Primitive, ReprFlags, ReprOptions, Scalar, Size, TagEncoding, TargetDataLayout,
TyAbiInterface, VariantIdx, Variants,
};
use rustc_error_messages::DiagMessage;
Expand Down Expand Up @@ -1169,6 +1169,11 @@ where
fn is_transparent(this: TyAndLayout<'tcx>) -> bool {
matches!(this.ty.kind(), ty::Adt(def, _) if def.repr().transparent())
}

/// See [`TyAndLayout::pass_indirectly_in_non_rustic_abis`] for details.
fn is_pass_indirectly_in_non_rustic_abis_flag_set(this: TyAndLayout<'tcx>) -> bool {
matches!(this.ty.kind(), ty::Adt(def, _) if def.repr().flags.contains(ReprFlags::PASS_INDIRECTLY_IN_NON_RUSTIC_ABIS))
}
}

/// Calculates whether a function's ABI can unwind or not.
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1524,6 +1524,14 @@ impl<'tcx> TyCtxt<'tcx> {
flags.insert(ReprFlags::IS_LINEAR);
}

// See `TyAndLayout::pass_indirectly_in_non_rustic_abis` for details.
if find_attr!(
self.get_all_attrs(did),
AttributeKind::RustcPassIndirectlyInNonRusticAbis(..)
) {
flags.insert(ReprFlags::PASS_INDIRECTLY_IN_NON_RUSTIC_ABIS);
}

ReprOptions { int: size, align: max_align, pack: min_pack, flags, field_shuffle_seed }
}

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::ObjcSelector { .. }
| AttributeKind::RustcCoherenceIsCore(..)
| AttributeKind::DebuggerVisualizer(..)
| AttributeKind::RustcPassIndirectlyInNonRusticAbis(..)
) => { /* do nothing */ }
Attribute::Unparsed(attr_item) => {
style = Some(attr_item.style);
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1935,6 +1935,7 @@ symbols! {
rustc_partition_codegened,
rustc_partition_reused,
rustc_pass_by_value,
rustc_pass_indirectly_in_non_rustic_abis,
rustc_peek,
rustc_peek_liveness,
rustc_peek_maybe_init,
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_target/src/callconv/aarch64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ where
// Not touching this...
return;
}
if arg.layout.pass_indirectly_in_non_rustic_abis(cx) {
arg.make_indirect();
return;
}
if !arg.layout.is_aggregate() {
if kind == AbiKind::DarwinPCS {
// On Darwin, when passing an i8/i16, it must be sign-extended to 32 bits,
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_target/src/callconv/amdgpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ where
ret.extend_integer_width_to(32);
}

fn classify_arg<'a, Ty, C>(_cx: &C, arg: &mut ArgAbi<'a, Ty>)
fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>)
where
Ty: TyAbiInterface<'a, C> + Copy,
C: HasDataLayout,
{
if arg.layout.pass_indirectly_in_non_rustic_abis(cx) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we rely on every single callconv getting this right, we're toast. It's way too easy to forget this somewhere.

Is there some way we can do this centrally for all ABIs?
For instance, we could apply this logic after the target-specific ABI stuff has been done.
Cc @workingjubilee

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The individual calling conventions sometimes still need to be aware of the parameters, to update the number of remaining general-purpose registers, and the current design of the calling convention code makes it hard to abstract this. Separately from this PR, I've been planning to refactor the calling convention handling a bit as even without this change there's a lot of code duplication already (all the compute_abi_info functions are essentially variants of the same function with calls to classify_arg and classify_ret); this refactoring should make it possible to do this in a more centralised way.

For now, this PR previously had a cfg!(debug_assertions)-guarded check at the end of adjust_for_foreign_abi in callconv/mod.rs that asserts that individual calling convention correctly set all the #[rustc_pass_indirectly_in_non_rustic_abis] arguments to be passed indirectly. I've updated the check so it now always run rather than just running when debug_assetions are enabled.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The individual calling conventions sometimes still need to be aware of the parameters, to update the number of remaining general-purpose registers,

Urgh, right, I forgot we need to care about low-level nonsense like that here. :/

Regarding refactoring the ABI code, also see #119183. I think @workingjubilee also has some thoughts in that direction. I'm happy to discuss design options and provide feedback.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the most robust idea, but we there could be some kind of ICE-causing bomb that gets defused when checking an arg's pass_indirectly_in_non_rustic_abis and ignored if there are no args. This at least makes sure that new targets don't get very far if they miss this important detail.

Or a codegen test that gets run on all targets?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have some code doing sanity checks on the ABI after it got computed. We could probably add an assertion there.

fn fn_abi_sanity_check<'tcx>(
cx: &LayoutCx<'tcx>,
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
spec_abi: ExternAbi,
) {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indirect_argument function would not be codegened if it doesn't get called, so that wouldn't trigger the ABI check. It should probably just be a ui test. This won't run in CI for tier 3 targets, but the worst that can happen is that you get an ICE, not a silent miscompilation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we make sure that triggers for every ABI though?

It's an unstable attribute, so worst case we get an ICE when building libcore. That seems fine.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indirect_argument function would not be codegened if it doesn't get called, so that wouldn't trigger the ABI check.

what if we use a const fn? Maybe using some const _: () = assert!(/* ... */), would that work?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using const does work, I added that and a fix for transparent wrappers ignoring the attribute at master...folkertdev:rust:pass-indirectly-attr-updates

@beetrees feel free to steal or chery-pick from that. I'd also happily force-push to this branch if you don't have time/interest. (this is on the critical path for c-variadics now that the error messages are in a good state, and I'd hate to waste the reviewer momentum).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've cherry-picked your patch for this.

arg.make_indirect();
return;
}
arg.extend_integer_width_to(32);
}

Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_target/src/callconv/arm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ where
// Not touching this...
return;
}
if arg.layout.pass_indirectly_in_non_rustic_abis(cx) {
arg.make_indirect();
return;
}
if !arg.layout.is_aggregate() {
arg.extend_integer_width_to(32);
return;
Expand Down
18 changes: 15 additions & 3 deletions compiler/rustc_target/src/callconv/avr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
//! compatible with AVR-GCC - Rust and AVR-GCC only differ in the small amount
//! of compiler frontend specific calling convention logic implemented here.
use rustc_abi::TyAbiInterface;

use crate::callconv::{ArgAbi, FnAbi};

fn classify_ret_ty<Ty>(ret: &mut ArgAbi<'_, Ty>) {
Expand All @@ -38,13 +40,23 @@ fn classify_ret_ty<Ty>(ret: &mut ArgAbi<'_, Ty>) {
}
}

fn classify_arg_ty<Ty>(arg: &mut ArgAbi<'_, Ty>) {
fn classify_arg_ty<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>)
where
Ty: TyAbiInterface<'a, C> + Copy,
{
if arg.layout.pass_indirectly_in_non_rustic_abis(cx) {
arg.make_indirect();
return;
}
if arg.layout.is_aggregate() {
arg.make_indirect();
}
}

pub(crate) fn compute_abi_info<Ty>(fty: &mut FnAbi<'_, Ty>) {
pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fty: &mut FnAbi<'a, Ty>)
where
Ty: TyAbiInterface<'a, C> + Copy,
{
if !fty.ret.is_ignore() {
classify_ret_ty(&mut fty.ret);
}
Expand All @@ -54,6 +66,6 @@ pub(crate) fn compute_abi_info<Ty>(fty: &mut FnAbi<'_, Ty>) {
continue;
}

classify_arg_ty(arg);
classify_arg_ty(cx, arg);
}
}
18 changes: 15 additions & 3 deletions compiler/rustc_target/src/callconv/bpf.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// see https://github.com/llvm/llvm-project/blob/main/llvm/lib/Target/BPF/BPFCallingConv.td
use rustc_abi::TyAbiInterface;

use crate::callconv::{ArgAbi, FnAbi};

fn classify_ret<Ty>(ret: &mut ArgAbi<'_, Ty>) {
Expand All @@ -9,15 +11,25 @@ fn classify_ret<Ty>(ret: &mut ArgAbi<'_, Ty>) {
}
}

fn classify_arg<Ty>(arg: &mut ArgAbi<'_, Ty>) {
fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>)
where
Ty: TyAbiInterface<'a, C> + Copy,
{
if arg.layout.pass_indirectly_in_non_rustic_abis(cx) {
arg.make_indirect();
return;
}
if arg.layout.is_aggregate() || arg.layout.size.bits() > 64 {
arg.make_indirect();
} else {
arg.extend_integer_width_to(32);
}
}

pub(crate) fn compute_abi_info<Ty>(fn_abi: &mut FnAbi<'_, Ty>) {
pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>)
where
Ty: TyAbiInterface<'a, C> + Copy,
{
if !fn_abi.ret.is_ignore() {
classify_ret(&mut fn_abi.ret);
}
Expand All @@ -26,6 +38,6 @@ pub(crate) fn compute_abi_info<Ty>(fn_abi: &mut FnAbi<'_, Ty>) {
if arg.is_ignore() {
continue;
}
classify_arg(arg);
classify_arg(cx, arg);
}
}
18 changes: 15 additions & 3 deletions compiler/rustc_target/src/callconv/csky.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// Reference: Clang CSKY lowering code
// https://github.com/llvm/llvm-project/blob/4a074f32a6914f2a8d7215d78758c24942dddc3d/clang/lib/CodeGen/Targets/CSKY.cpp#L76-L162

use rustc_abi::TyAbiInterface;

use crate::callconv::{ArgAbi, FnAbi, Reg, Uniform};

fn classify_ret<Ty>(arg: &mut ArgAbi<'_, Ty>) {
Expand All @@ -27,11 +29,18 @@ fn classify_ret<Ty>(arg: &mut ArgAbi<'_, Ty>) {
}
}

fn classify_arg<Ty>(arg: &mut ArgAbi<'_, Ty>) {
fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>)
where
Ty: TyAbiInterface<'a, C> + Copy,
{
if !arg.layout.is_sized() {
// Not touching this...
return;
}
if arg.layout.pass_indirectly_in_non_rustic_abis(cx) {
arg.make_indirect();
return;
}
// For argument type, the first 4*XLen parts of aggregate will be passed
// in registers, and the rest will be passed in stack.
// So we can coerce to integers directly and let backend handle it correctly.
Expand All @@ -47,7 +56,10 @@ fn classify_arg<Ty>(arg: &mut ArgAbi<'_, Ty>) {
}
}

pub(crate) fn compute_abi_info<Ty>(fn_abi: &mut FnAbi<'_, Ty>) {
pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>)
where
Ty: TyAbiInterface<'a, C> + Copy,
{
if !fn_abi.ret.is_ignore() {
classify_ret(&mut fn_abi.ret);
}
Expand All @@ -56,6 +68,6 @@ pub(crate) fn compute_abi_info<Ty>(fn_abi: &mut FnAbi<'_, Ty>) {
if arg.is_ignore() {
continue;
}
classify_arg(arg);
classify_arg(cx, arg);
}
}
18 changes: 15 additions & 3 deletions compiler/rustc_target/src/callconv/hexagon.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use rustc_abi::TyAbiInterface;

use crate::callconv::{ArgAbi, FnAbi};

fn classify_ret<Ty>(ret: &mut ArgAbi<'_, Ty>) {
Expand All @@ -8,15 +10,25 @@ fn classify_ret<Ty>(ret: &mut ArgAbi<'_, Ty>) {
}
}

fn classify_arg<Ty>(arg: &mut ArgAbi<'_, Ty>) {
fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>)
where
Ty: TyAbiInterface<'a, C> + Copy,
{
if arg.layout.pass_indirectly_in_non_rustic_abis(cx) {
arg.make_indirect();
return;
}
if arg.layout.is_aggregate() && arg.layout.size.bits() > 64 {
arg.make_indirect();
} else {
arg.extend_integer_width_to(32);
}
}

pub(crate) fn compute_abi_info<Ty>(fn_abi: &mut FnAbi<'_, Ty>) {
pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>)
where
Ty: TyAbiInterface<'a, C> + Copy,
{
if !fn_abi.ret.is_ignore() {
classify_ret(&mut fn_abi.ret);
}
Expand All @@ -25,6 +37,6 @@ pub(crate) fn compute_abi_info<Ty>(fn_abi: &mut FnAbi<'_, Ty>) {
if arg.is_ignore() {
continue;
}
classify_arg(arg);
classify_arg(cx, arg);
}
}
Loading
Loading