%----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%----------------------------------------------------------------------------%
% Copyright (C) 2009-2012 The University of Melbourne.
% Copyright (C) 2013-2018, 2024-2026 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%----------------------------------------------------------------------------%
%
% File: llds_out_data.m.
% Main authors: conway, fjh, zs.
%
% This module defines the routines for printing out LLDS lvals, rvals,
% and global variables.
%
%----------------------------------------------------------------------------%

:- module ll_backend.llds_out.llds_out_data.
:- interface.

:- import_module hlds.
:- import_module hlds.hlds_llds.
:- import_module ll_backend.llds.
:- import_module ll_backend.llds_out.llds_out_util.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.

:- import_module bool.
:- import_module io.
:- import_module list.

%----------------------------------------------------------------------------%
%
% Lvals.
%

    % output_lval_decls(Lval, ...) outputs the declarations of any
    % static constants, etc. that need to be declared before
    % output_lval(Lval) is called.
    %
:- pred output_record_lval_decls(llds_out_info::in,
    io.text_output_stream::in, lval::in,
    decl_set::in, decl_set::out, io::di, io::uo) is det.
:- pred output_record_lval_decls_tab(llds_out_info::in,
    io.text_output_stream::in, lval::in,
    decl_set::in, decl_set::out, io::di, io::uo) is det.

:- pred output_lval(llds_out_info::in, io.text_output_stream::in,
    lval::in, io::di, io::uo) is det.

:- pred output_lval_for_assign(llds_out_info::in, io.text_output_stream::in,
    lval::in, llds_type::out, io::di, io::uo) is det.

:- pred output_lval_as_word(llds_out_info::in, io.text_output_stream::in,
    lval::in, io::di, io::uo) is det.

    % Return the C "name" the given llds_type. (The "name" is in quotes
    % because the result may be more than one word.)
    %
:- func llds_type_to_string(llds_type) = string.

    % Convert an lval to a string description of that lval.
    %
:- pred lval_to_string(lval::in, string::out) is semidet.

    % Convert a register to a string description of that register.
    %
:- func reg_to_string(reg_type, int) = string.

:- func c_global_var_name(c_global_var_ref) = string.

%----------------------------------------------------------------------------%
%
% Rvals.
%

    % output_record_rval_decls(Info, Rval, !DeclSet) outputs the declarations
    % of any static constants, etc. that need to be declared before
    % output_rval(Rval) is called.
    %
:- pred output_record_rval_decls(llds_out_info::in,
    io.text_output_stream::in, rval::in,
    decl_set::in, decl_set::out, io::di, io::uo) is det.
:- pred output_record_rval_decls_tab(llds_out_info::in,
    io.text_output_stream::in, rval::in,
    decl_set::in, decl_set::out, io::di, io::uo) is det.

:- pred output_record_rvals_decls(llds_out_info::in, io.text_output_stream::in,
    list(rval)::in, decl_set::in, decl_set::out, io::di, io::uo) is det.

    % Output an rval, not converted to any particular type,
    % but instead output as its "natural" type.
    %
:- pred output_rval(llds_out_info::in, rval::in, io.text_output_stream::in,
    io::di, io::uo) is det.

    % Output an rval, converted to the specified type
    %
:- pred output_rval_as_type(llds_out_info::in, rval::in, llds_type::in,
    io.text_output_stream::in, io::di, io::uo) is det.

:- pred output_test_rval(llds_out_info::in, rval::in,
    io.text_output_stream::in, io::di, io::uo) is det.

    % Write out the given ptag, wrapped up in MR_mktag(_).
    %
:- pred output_ptag(io.text_output_stream::in, ptag::in,
    io::di, io::uo) is det.

    % Write out the given ptag.
    %
:- pred write_ptag(io.text_output_stream::in, ptag::in, io::di, io::uo) is det.

    % Return true iff an integer constant can be used directly as a value
    % in a structure field of the given type, instead of being cast to
    % MR_Integer first and then to the type. The answer can be
    % conservative: it is always ok to return `no'.
    %
    % Only the compiler generates values of the uint_leastN types,
    % and for these the constant will never be negative.
    %
:- func direct_field_int_constant(llds_type) = bool.

%----------------------------------------------------------------------------%
%
% Global data.
%

    % Given an rval, succeed iff it is a floating point constant expression;
    % if so, return a name for that rval that is suitable for use in a C
    % identifier. Different rvals must be given different names.
    %
:- pred float_const_expr_name(rval::in, string::out) is semidet.

:- pred int64_const_expr_name(rval::in, string::out) is semidet.

:- pred uint64_const_expr_name(rval::in, string::out) is semidet.

    % output_record_data_addr_decls(Info, DataId, ...) outputs the
    % declarations of any static constants, etc. that need to be declared
    % before output_data_id(Info DataId, ...) is called.
    %
:- pred output_record_data_id_decls(llds_out_info::in,
    io.text_output_stream::in, data_id::in,
    decl_set::in, decl_set::out, io::di, io::uo) is det.
:- pred output_record_data_id_decls_format(llds_out_info::in,
    io.text_output_stream::in, data_id::in, string::in, string::in,
    int::in, int::out, decl_set::in, decl_set::out, io::di, io::uo) is det.

    % Output the name of the global variable identified by the data_id.
    %
:- pred output_data_id(llds_out_info::in, io.text_output_stream::in,
    data_id::in, io::di, io::uo) is det.

    % Output the address of the global variable identified by the data_id.
    %
:- pred output_data_id_addr(llds_out_info::in, io.text_output_stream::in,
    data_id::in, io::di, io::uo) is det.

:- pred output_common_scalar_cell_array_name(io.text_output_stream::in,
    type_num::in, io::di, io::uo) is det.

:- pred output_common_vector_cell_array_name(io.text_output_stream::in,
    type_num::in, int::in, io::di, io::uo) is det.

%----------------------------------------------------------------------------%
%----------------------------------------------------------------------------%

:- implementation.

:- import_module backend_libs.
:- import_module backend_libs.builtin_ops.
:- import_module backend_libs.c_util.
:- import_module backend_libs.name_mangle.
:- import_module backend_libs.rtti.
:- import_module libs.
:- import_module libs.optimization_options.
:- import_module ll_backend.layout_out.
:- import_module ll_backend.llds_out.llds_out_code_addr.
:- import_module ll_backend.rtti_out.
:- import_module mdbcomp.
:- import_module mdbcomp.builtin_modules.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.prog_foreign.

:- import_module int.
:- import_module map.
:- import_module maybe.
:- import_module require.
:- import_module string.
:- import_module uint.
:- import_module uint16.
:- import_module uint8.

%----------------------------------------------------------------------------%
%
% Declaring lvals.
%

output_record_lval_decls(Info, Stream, Lval, !DeclSet, !IO) :-
    output_record_lval_decls_format(Info, Stream, Lval,
        "", "", 0, _, !DeclSet, !IO).

output_record_lval_decls_tab(Info, Stream, Lval, !DeclSet, !IO) :-
    output_record_lval_decls_format(Info, Stream, Lval,
        "\t", "\t", 0, _, !DeclSet, !IO).

:- pred output_record_lval_decls_format(llds_out_info::in,
    io.text_output_stream::in, lval::in,
    string::in, string::in, int::in, int::out, decl_set::in, decl_set::out,
    io::di, io::uo) is det.

output_record_lval_decls_format(Info, Stream, Lval, FirstIndent, LaterIndent,
        !N, !DeclSet, !IO) :-
    (
        Lval = field(_, Rval, FieldNum),
        output_record_rval_decls_format(Info, Stream, Rval,
            FirstIndent, LaterIndent, !N, !DeclSet, !IO),
        output_record_rval_decls_format(Info, Stream, FieldNum,
            FirstIndent, LaterIndent, !N, !DeclSet, !IO)
    ;
        ( Lval = succfr_slot(Rval)
        ; Lval = prevfr_slot(Rval)
        ; Lval = redofr_slot(Rval)
        ; Lval = redoip_slot(Rval)
        ; Lval = succip_slot(Rval)
        ; Lval = mem_ref(Rval)
        ),
        output_record_rval_decls_format(Info, Stream, Rval,
            FirstIndent, LaterIndent, !N, !DeclSet, !IO)
    ;
        ( Lval = reg(_, _)
        ; Lval = stackvar(_)
        ; Lval = parent_stackvar(_)
        ; Lval = framevar(_)
        ; Lval = double_stackvar(_, _)
        ; Lval = succip
        ; Lval = maxfr
        ; Lval = curfr
        ; Lval = hp
        ; Lval = sp
        ; Lval = parent_sp
        ; Lval = lvar(_)
        ; Lval = temp(_, _)
        )
    ;
        Lval = global_var_ref(CGlobalVar),
        ( if decl_set_is_member(decl_c_global_var(CGlobalVar), !.DeclSet) then
            true
        else
            % All env_var_ref global_var_refs should have been output by
            % output_c_procedure_decls already, and as of now there are no
            % other global_var_refs.
            unexpected($file, $pred, "global_var_ref")
        )
    ).

%----------------------------------------------------------------------------%
%
% Writing lvals.
%

output_lval(Info, Stream, Lval, !IO) :-
    (
        Lval = reg(Type, Num),
        output_reg(Stream, Type, Num, !IO)
    ;
        Lval = stackvar(N),
        ( if N =< 0 then
            unexpected($file, $pred, "stack var out of range")
        else
            true
        ),
        io.format(Stream, "MR_sv(%d)", [i(N)], !IO)
    ;
        Lval = parent_stackvar(N),
        ( if N =< 0 then
            unexpected($file, $pred, "parent stack var out of range")
        else
            true
        ),
        io.format(Stream, "MR_parent_sv(%d)", [i(N)], !IO)
    ;
        Lval = framevar(N),
        ( if N =< 0 then
            unexpected($file, $pred, "frame var out of range")
        else
            true
        ),
        io.format(Stream, "MR_fv(%d)", [i(N)], !IO)
    ;
        Lval = double_stackvar(StackType, SlotNum),
        io.write_string(Stream, "MR_float_from_dword_ptr(", !IO),
        output_double_stackvar_ptr(Info, Stream, StackType, SlotNum, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = succip,
        io.write_string(Stream, "MR_succip", !IO)
    ;
        Lval = sp,
        io.write_string(Stream, "MR_sp", !IO)
    ;
        Lval = parent_sp,
        io.write_string(Stream, "MR_parent_sp", !IO)
    ;
        Lval = hp,
        io.write_string(Stream, "MR_hp", !IO)
    ;
        Lval = maxfr,
        io.write_string(Stream, "MR_maxfr", !IO)
    ;
        Lval = curfr,
        io.write_string(Stream, "MR_curfr", !IO)
    ;
        Lval = succfr_slot(Rval),
        io.write_string(Stream, "MR_succfr_slot(", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = prevfr_slot(Rval),
        io.write_string(Stream, "MR_prevfr_slot(", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = redofr_slot(Rval),
        io.write_string(Stream, "MR_redofr_slot(", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = redoip_slot(Rval),
        io.write_string(Stream, "MR_redoip_slot(", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = succip_slot(Rval),
        io.write_string(Stream, "MR_succip_slot(", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = field(MaybePtag, Rval, FieldNumRval),
        (
            MaybePtag = yes(ptag(PtagUInt8)),
            io.format(Stream, "MR_tfield(%u, ", [u8(PtagUInt8)], !IO)
        ;
            MaybePtag = no,
            io.write_string(Stream, "MR_mask_field(", !IO)
        ),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ", ", !IO),
        ( if FieldNumRval = const(llconst_int(FieldNum)) then
            % Avoid emitting the (MR_Integer) cast.
            io.write_int(Stream, FieldNum, !IO)
        else
            output_rval(Info, FieldNumRval, Stream, !IO)
        ),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = lvar(_),
        unexpected($file, $pred, "lvar")
    ;
        Lval = temp(Type, Num),
        (
            Type = reg_r,
            io.write_string(Stream, "MR_tempr", !IO),
            io.write_int(Stream, Num, !IO)
        ;
            Type = reg_f,
            io.write_string(Stream, "MR_tempf", !IO),
            io.write_int(Stream, Num, !IO)
        )
    ;
        Lval = mem_ref(Rval),
        io.write_string(Stream, "* (MR_Word *) (", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = global_var_ref(GlobalVar),
        io.write_string(Stream, c_global_var_name(GlobalVar), !IO)
    ).

output_lval_for_assign(Info, Stream, Lval, Type, !IO) :-
    (
        Lval = reg(RegType, Num),
        (
            RegType = reg_r,
            Type = lt_word
        ;
            RegType = reg_f,
            Type = lt_float
        ),
        output_reg(Stream, RegType, Num, !IO)
    ;
        Lval = stackvar(N),
        Type = lt_word,
        ( if N < 0 then
            unexpected($file, $pred, "stack var out of range")
        else
            true
        ),
        io.format(Stream, "MR_sv(%d)", [i(N)], !IO)
    ;
        Lval = parent_stackvar(N),
        Type = lt_word,
        ( if N < 0 then
            unexpected($file, $pred, "parent stack var out of range")
        else
            true
        ),
        io.format(Stream, "MR_parent_sv(%d)", [i(N)], !IO)
    ;
        Lval = framevar(N),
        Type = lt_word,
        ( if N =< 0 then
            unexpected($file, $pred, "frame var out of range")
        else
            true
        ),
        io.format(Stream, "MR_fv(%d)", [i(N)], !IO)
    ;
        Lval = double_stackvar(StackType, SlotNum),
        Type = lt_float,
        io.write_string(Stream, "* (MR_Float *) ", !IO),
        output_double_stackvar_ptr(Info, Stream, StackType, SlotNum, !IO)
    ;
        Lval = succip,
        Type = lt_word,
        io.write_string(Stream, "MR_succip_word", !IO)
    ;
        Lval = sp,
        Type = lt_word,
        io.write_string(Stream, "MR_sp_word", !IO)
    ;
        Lval = parent_sp,
        Type = lt_data_ptr,
        io.write_string(Stream, "MR_parent_sp", !IO)
    ;
        Lval = hp,
        Type = lt_word,
        io.write_string(Stream, "MR_hp_word", !IO)
    ;
        Lval = maxfr,
        Type = lt_word,
        io.write_string(Stream, "MR_maxfr_word", !IO)
    ;
        Lval = curfr,
        Type = lt_word,
        io.write_string(Stream, "MR_curfr_word", !IO)
    ;
        Lval = succfr_slot(Rval),
        Type = lt_word,
        io.write_string(Stream, "MR_succfr_slot_word(", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = prevfr_slot(Rval),
        Type = lt_word,
        io.write_string(Stream, "MR_prevfr_slot_word(", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = redofr_slot(Rval),
        Type = lt_word,
        io.write_string(Stream, "MR_redofr_slot_word(", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = redoip_slot(Rval),
        Type = lt_word,
        io.write_string(Stream, "MR_redoip_slot_word(", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = succip_slot(Rval),
        Type = lt_word,
        io.write_string(Stream, "MR_succip_slot_word(", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = field(MaybePtag, Rval, FieldNumRval),
        Type = lt_word,
        (
            MaybePtag = yes(ptag(PtagUInt8)),
            io.format(Stream, "MR_tfield(%u, ", [u8(PtagUInt8)], !IO)
        ;
            MaybePtag = no,
            io.write_string(Stream, "MR_mask_field(", !IO)
        ),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ", ", !IO),
        ( if FieldNumRval = const(llconst_int(FieldNum)) then
            % Avoid emitting the (MR_Integer) cast.
            io.write_int(Stream, FieldNum, !IO)
        else
            output_rval(Info, FieldNumRval, Stream, !IO)
        ),
        io.write_string(Stream, ")", !IO)
    ;
        Lval = lvar(_),
        unexpected($file, $pred, "lvar")
    ;
        Lval = temp(RegType, Num),
        (
            RegType = reg_r,
            Type = lt_word,
            io.write_string(Stream, "MR_tempr", !IO),
            io.write_int(Stream, Num, !IO)
        ;
            RegType = reg_f,
            Type = lt_float,
            io.write_string(Stream, "MR_tempf", !IO),
            io.write_int(Stream, Num, !IO)
        )
    ;
        Lval = mem_ref(_MemRef),
        Type = lt_word,
        output_lval(Info, Stream, Lval, !IO)
    ;
        Lval = global_var_ref(GlobalVar),
        Type = lt_word,
        io.write_string(Stream, c_global_var_name(GlobalVar), !IO)
    ).

output_lval_as_word(Info, Stream, Lval, !IO) :-
    llds.lval_type(Lval, ActualType),
    ( if llds_types_match(lt_word, ActualType) then
        output_lval(Info, Stream, Lval, !IO)
    else if ActualType = lt_float then
        % Sanity check -- if this happens, the LLDS is ill-typed.
        unexpected($file, $pred, "float")
    else
        io.write_string(Stream, "MR_LVALUE_CAST(MR_Word,", !IO),
        output_lval(Info, Stream, Lval, !IO),
        io.write_string(Stream, ")", !IO)
    ).

:- pred output_double_stackvar_ptr(llds_out_info::in,
    io.text_output_stream::in, double_stack_type::in, int::in,
    io::di, io::uo) is det.

output_double_stackvar_ptr(Info, Stream, StackType, SlotNum, !IO) :-
    % The higher-numbered slot has the lower address because our stacks grow
    % downwards.
    (
        StackType = double_stackvar,
        Lval = stackvar(SlotNum + 1)
    ;
        StackType = double_parent_stackvar,
        Lval = parent_stackvar(SlotNum + 1)
    ),
    io.write_string(Stream, "MR_dword_ptr(&(", !IO),
    output_lval(Info, Stream, Lval, !IO),
    io.write_string(Stream, "))", !IO).

    % llds_types_match(DesiredType, ActualType) is true iff
    % a value of type ActualType can be used as a value of
    % type DesiredType without casting.
    %
:- pred llds_types_match(llds_type::in, llds_type::in) is semidet.

llds_types_match(DesiredType, ActualType) :-
    ( if DesiredType = ActualType then
        true
    else
        % I (zs) think it possible that some desired/actual type pairs
        % are missing from here.
        (
            ( DesiredType = lt_word
            ; DesiredType = lt_bool
            ),
            ( ActualType = lt_int(int_type_int)
            ; ActualType = lt_int(int_type_uint)
            ; ActualType = lt_word
            )
        ;
            DesiredType = lt_int(int_type_int),
            ActualType = lt_bool
        )
    ).

    % Output the given llds_type with parentheses around it.
    %
:- pred output_llds_type_cast(io.text_output_stream::in, llds_type::in,
    io::di, io::uo) is det.

output_llds_type_cast(Stream, Type, !IO) :-
    TypeStr = llds_type_to_string(Type),
    io.format(Stream, "(%s) ", [s(TypeStr)], !IO).

llds_type_to_string(Type) = Str :-
    (
        Type = lt_int_least(IntLeastType),
        ( IntLeastType = int_least8,    Str = "MR_int_least8_t"
        ; IntLeastType = int_least16,   Str = "MR_int_least16_t"
        ; IntLeastType = int_least32,   Str = "MR_int_least32_t"
        ; IntLeastType = uint_least8,   Str = "MR_uint_least8_t"
        ; IntLeastType = uint_least16,  Str = "MR_uint_least16_t"
        ; IntLeastType = uint_least32,  Str = "MR_uint_least32_t"
        )
    ;
        Type = lt_int(IntType),
        ( IntType = int_type_int,       Str = "MR_Integer"
        ; IntType = int_type_int8,      Str = "int8_t"
        ; IntType = int_type_int16,     Str = "int16_t"
        ; IntType = int_type_int32,     Str = "int32_t"
        ; IntType = int_type_int64,     Str = "int64_t"
        ; IntType = int_type_uint,      Str = "MR_Unsigned"
        ; IntType = int_type_uint8,     Str = "uint8_t"
        ; IntType = int_type_uint16,    Str = "uint16_t"
        ; IntType = int_type_uint32,    Str = "uint32_t"
        ; IntType = int_type_uint64,    Str = "uint64_t"
        )
    ;
        Type = lt_float,                Str = "MR_Float"
    ;
        Type = lt_string,               Str = "MR_String"
    ;
        Type = lt_bool,                 Str = "MR_Integer"
    ;
        Type = lt_word,                 Str = "MR_Word"
    ;
        Type = lt_data_ptr,             Str = "MR_Word *"
    ;
        Type = lt_code_ptr,             Str = "MR_Code *"
    ).

lval_to_string(Lval, Str) :-
    require_switch_arms_det [Lval]
    (
        Lval = reg(RegType, RegNum),
        Str = reg_to_string(RegType, RegNum)
    ;
        Lval = succip,
        Str = "succip"
    ;
        Lval = maxfr,
        Str = "maxfr"
    ;
        Lval = curfr,
        Str = "curfr"
    ;
        Lval = hp,
        Str = "hp"
    ;
        Lval = sp,
        Str = "sp"
    ;
        Lval = parent_sp,
        Str = "parent_sp"
    ;
        Lval = temp(RegType, N),
        ( RegType = reg_r, RegTypeStr = "r"
        ; RegType = reg_f, RegTypeStr = "f"
        ),
        string.format("temp(%s, %d)", [s(RegTypeStr), i(N)], Str)
    ;
        Lval = stackvar(N),
        Str = "MR_sv(" ++ int_to_string(N) ++ ")"
    ;
        Lval = parent_stackvar(N),
        Str = "MR_parent_sv(" ++ int_to_string(N) ++ ")"
    ;
        Lval = framevar(N),
        Str = "MR_fv(" ++ int_to_string(N) ++ ")"
    ;
        Lval = double_stackvar(Type, N),
        (
            Type = double_stackvar,
            Macro = "MR_sv"
        ;
            Type = double_parent_stackvar,
            Macro = "MR_parent_sv"
        ),
        string.format("%s(%d,%d)", [s(Macro), i(N), i(N + 1)], Str)
    ).

reg_to_string(reg_r, N) =
    ( if N =< max_real_r_reg then
        "MR_r" ++ int_to_string(N)
    else if N =< max_virtual_r_reg then
        "MR_r(" ++ int_to_string(N) ++ ")"
    else
        unexpected($file, $pred, "register number too large")
    ).
reg_to_string(reg_f, N) =
    ( if N =< max_virtual_f_reg then
        "MR_f(" ++ int_to_string(N) ++ ")"
    else
        unexpected($file, $pred, "register number too large")
    ).

:- func max_real_r_reg = int.
:- func max_virtual_r_reg = int.

max_real_r_reg = 32.
max_virtual_r_reg = 1024.

:- func max_virtual_f_reg = int.

max_virtual_f_reg = 1024.

:- pred output_reg(io.text_output_stream::in, reg_type::in, int::in,
    io::di, io::uo) is det.

output_reg(Stream, RegType, N, !IO) :-
    io.write_string(Stream, reg_to_string(RegType, N), !IO).

% The calls to env_var_is_acceptable_char in parse_goal.m ensure that
% EnvVarName is acceptable as part of a C identifier.
% The prefix must be identical to envvar_prefix in util/mkinit.c and
% global_var_name in mlds_to_c.m.
c_global_var_name(env_var_ref(EnvVarName)) = "mercury_envvar_" ++ EnvVarName.

%----------------------------------------------------------------------------%
%
% Declaring rvals.
%

output_record_rval_decls(Info, Stream, Rval, !DeclSet, !IO) :-
    output_record_rval_decls_format(Info, Stream, Rval,
        "", "", 0, _, !DeclSet, !IO).

output_record_rval_decls_tab(Info, Stream, Rval, !DeclSet, !IO) :-
    output_record_rval_decls_format(Info, Stream, Rval,
        "", "\t", 0, _, !DeclSet, !IO).

    % output_record_rval_decls_format(Info, Rval, FirstIndent, LaterIndent,
    %   !N, !DeclSet, !IO)
    %
    % Outputs the declarations of any static constants, etc. that need to be
    % declared before output_rval(Rval) is called. FirstIndent is output
    % before the first declaration, while LaterIndent is output before
    % all later declaration; N0 and N give the number of declarations output
    % before and after this call.
    %
    % Every time we emit a declaration for a symbol, we insert it into the
    % set of symbols we have already declared. That way, we avoid generating
    % the same symbol twice, which would cause an error in the C code.
    %
:- pred output_record_rval_decls_format(llds_out_info::in,
    io.text_output_stream::in, rval::in,
    string::in, string::in, int::in, int::out,
    decl_set::in, decl_set::out, io::di, io::uo) is det.

output_record_rval_decls_format(Info, Stream, Rval, FirstIndent, LaterIndent,
        !N, !DeclSet, !IO) :-
    (
        Rval = lval(Lval),
        output_record_lval_decls_format(Info, Stream, Lval,
            FirstIndent, LaterIndent, !N, !DeclSet, !IO)
    ;
        Rval = var(_),
        unexpected($file, $pred, "var")
    ;
        Rval = mkword_hole(_)
    ;
        Rval = const(Const),
        (
            Const = llconst_code_addr(CodeAddress),
            output_record_code_addr_decls_format(Info, Stream, CodeAddress,
                FirstIndent, LaterIndent, !N, !DeclSet, !IO)
        ;
            ( Const = llconst_data_addr(DataId)
            ; Const = llconst_data_addr_word_offset(DataId, _)
            ),
            output_record_data_id_decls_format(Info, Stream, DataId,
                FirstIndent, LaterIndent, !N, !DeclSet, !IO)
        ;
            Const = llconst_float(FloatVal),
            % If floats are boxed, but are allocated statically, then for each
            % float constant which we might want to box, we declare a static
            % const variable holding that constant.
            UnboxedFloat = Info ^ lout_unboxed_float,
            StaticGroundFloats = Info ^ lout_static_ground_floats,
            ( if
                UnboxedFloat = no,
                StaticGroundFloats = use_static_ground_floats,
                float_literal_name(FloatVal, FloatName),
                FloatLabel = decl_float_label(FloatName),
                decl_set_insert_new(FloatLabel, !DeclSet)
            then
                FloatValStr = c_util.make_float_literal(FloatVal),
                output_indent(Stream, FirstIndent, LaterIndent, !.N, !IO),
                !:N = !.N + 1,
                io.format(Stream,
                    "static const MR_Float mercury_float_const_%s = %s;\n",
                    [s(FloatName), s(FloatValStr)], !IO)
            else
                true
            )
        ;
            Const = llconst_int64(Int64Val),
            UnboxedInt64s = Info ^ lout_unboxed_int64s,
            StaticGroundInt64s = Info ^ lout_static_ground_int64s,
            ( if
                UnboxedInt64s = no,
                StaticGroundInt64s = use_static_ground_int64s,
                Int64Label = decl_int64_label(Int64Val),
                decl_set_insert_new(Int64Label, !DeclSet)
            then
                int64_literal_name(Int64Val, Int64Name),
                Int64ValStr = c_util.make_int64_literal(Int64Val),
                output_indent(Stream, FirstIndent, LaterIndent, !.N, !IO),
                !:N = !.N + 1,
                io.format(Stream,
                    "static const int64_t mercury_int64_const_%s = %s;\n",
                    [s(Int64Name), s(Int64ValStr)], !IO)
            else
                true
            )
        ;
            Const = llconst_uint64(UInt64Val),
            UnboxedInt64s = Info ^ lout_unboxed_int64s,
            StaticGroundInt64s = Info ^ lout_static_ground_int64s,
            ( if
                UnboxedInt64s = no,
                StaticGroundInt64s = use_static_ground_int64s,
                UInt64Label = decl_uint64_label(UInt64Val),
                decl_set_insert_new(UInt64Label, !DeclSet)
            then
                decl_set_insert(UInt64Label, !DeclSet),
                uint64_literal_name(UInt64Val, UInt64Name),
                UInt64ValStr = c_util.make_uint64_literal(UInt64Val),
                output_indent(Stream, FirstIndent, LaterIndent, !.N, !IO),
                !:N = !.N + 1,
                io.format(Stream,
                    "static const uint64_t mercury_uint64_const_%s = %s;\n",
                    [s(UInt64Name), s(UInt64ValStr)], !IO)
            else
                true
            )
        ;
            ( Const = llconst_true
            ; Const = llconst_false
            ; Const = llconst_int(_)
            ; Const = llconst_uint(_)
            ; Const = llconst_int8(_)
            ; Const = llconst_uint8(_)
            ; Const = llconst_int16(_)
            ; Const = llconst_uint16(_)
            ; Const = llconst_int32(_)
            ; Const = llconst_uint32(_)
            ; Const = llconst_foreign(_, _)
            ; Const = llconst_string(_)
            ; Const = llconst_multi_string(_)
            )
        )
    ;
        ( Rval = mkword(_, SubRval)
        ; Rval = cast(_, SubRval)
        ; Rval = unop(_, SubRval)
        ),
        output_record_rval_decls_format(Info, Stream, SubRval,
            FirstIndent, LaterIndent, !N, !DeclSet, !IO)
    ;
        Rval = binop(Op, SubRvalA, SubRvalB),
        output_record_rval_decls_format(Info, Stream, SubRvalA,
            FirstIndent, LaterIndent, !N, !DeclSet, !IO),
        output_record_rval_decls_format(Info, Stream, SubRvalB,
            FirstIndent, LaterIndent, !N, !DeclSet, !IO),

        % If floats are boxed, and the static ground terms option is enabled,
        % then for each float constant which we might want to box, we declare
        % a static const variable holding that constant.
        (
            Op = float_arith(ArithOp),
            OpStr = arith_op_c_operator(coerce(ArithOp)),
            UnboxFloat = Info ^ lout_unboxed_float,
            StaticGroundFloats = Info ^ lout_static_ground_floats,
            ( if
                UnboxFloat = no,
                StaticGroundFloats = use_static_ground_floats,
                float_const_binop_expr_name(Op, SubRvalA, SubRvalB, FloatName),
                FloatLabel = decl_float_label(FloatName),
                decl_set_insert_new(FloatLabel, !DeclSet)
            then
                output_indent(Stream, FirstIndent, LaterIndent, !.N, !IO),
                !:N = !.N + 1,
                FloatTypeStr = llds_type_to_string(lt_float),
                io.format(Stream, "static const %s mercury_float_const_%s = ",
                    [s(FloatTypeStr), s(FloatName)], !IO),
                % Note that we just output the expression here, and
                % let the C compiler evaluate it, rather than evaluating
                % it ourselves. This avoids having to deal with some nasty
                % issues regarding floating point accuracy when doing
                % cross-compilation.
                output_rval_as_type(Info, SubRvalA, lt_float, Stream, !IO),
                io.format(Stream, " %s ", [s(OpStr)], !IO),
                output_rval_as_type(Info, SubRvalB, lt_float, Stream, !IO),
                io.write_string(Stream, ";\n", !IO)
            else
                true
            )
        ;
            ( Op = int_arith(_, _)
            ; Op = int_cmp(_, _)
            ; Op = int_as_uint_cmp(_)
            ; Op = in_range
            ; Op = float_cmp(_)
            ; Op = str_cmp(_)
            ; Op = str_nzp
            ; Op = unchecked_left_shift(_, _)
            ; Op = unchecked_right_shift(_, _)
            ; Op = bitwise_and(_)
            ; Op = bitwise_or(_)
            ; Op = bitwise_xor(_)
            ; Op = logical_and
            ; Op = logical_or
            ; Op = offset_str_eq(_, _)
            ; Op = array_index(_)
            ; Op = string_unsafe_index_code_unit
            ; Op = pointer_equal_conservative
            ; Op = body
            ; Op = float_from_dword
            ; Op = int64_from_dword
            ; Op = uint64_from_dword
            )
        )
    ;
        Rval = mem_addr(MemRef),
        output_record_mem_ref_decls_format(Info, Stream, MemRef,
            FirstIndent, LaterIndent, !N, !DeclSet, !IO)
    ).

output_record_rvals_decls(Info, Stream, Rvals, !DeclSet, !IO) :-
    output_record_rvals_decls_format(Info, Stream, Rvals, "", "", 0, _,
        !DeclSet, !IO).

:- pred output_record_rvals_decls_format(llds_out_info::in,
    io.text_output_stream::in, list(rval)::in,
    string::in, string::in, int::in, int::out, decl_set::in,
    decl_set::out, io::di, io::uo) is det.

output_record_rvals_decls_format(_, _, [], _, _, !N, !DeclSet, !IO).
output_record_rvals_decls_format(Info, Stream, Rvals @ [_ | _],
        FirstIndent, LaterIndent, !N, !DeclSet, !IO) :-
    output_record_rvals_decls_format_count(Info, Stream, Rvals, LeftOverRvals,
        1000, FirstIndent, LaterIndent, !N, !DeclSet, !IO),
    output_record_rvals_decls_format(Info, Stream, LeftOverRvals,
        FirstIndent, LaterIndent, !N, !DeclSet, !IO).

    % We use this predicate to output the declarations of up to Count rvals.
    % It is separate from output_record_rvals_decls_format so that in grades
    % that do not permit tail recursion, we can free up the stack frames
    % occupied by a bunch of loop iterations before the declarations of *all*
    % the rvals have been output.
    %
:- pred output_record_rvals_decls_format_count(llds_out_info::in,
    io.text_output_stream::in, list(rval)::in, list(rval)::out, int::in,
    string::in, string::in, int::in, int::out, decl_set::in,
    decl_set::out, io::di, io::uo) is det.

output_record_rvals_decls_format_count(_, _, [], [], _, _, _,
        !N, !DeclSet, !IO).
output_record_rvals_decls_format_count(Info, Stream, [Rval | Rvals],
        LeftOverRvals, Count, FirstIndent, LaterIndent, !N, !DeclSet, !IO) :-
    ( if Count > 0 then
        output_record_rval_decls_format(Info, Stream, Rval,
            FirstIndent, LaterIndent, !N, !DeclSet, !IO),
        output_record_rvals_decls_format_count(Info, Stream, Rvals,
            LeftOverRvals, Count - 1, FirstIndent, LaterIndent,
            !N, !DeclSet, !IO)
    else
        LeftOverRvals = [Rval | Rvals]
    ).

:- pred output_record_mem_ref_decls_format(llds_out_info::in,
    io.text_output_stream::in, mem_ref::in, string::in, string::in,
    int::in, int::out, decl_set::in, decl_set::out, io::di, io::uo) is det.

output_record_mem_ref_decls_format(Info, Stream, MemRef,
        FirstIndent, LaterIndent, !N, !DeclSet, !IO) :-
    (
        ( MemRef = stackvar_ref(Rval)
        ; MemRef = framevar_ref(Rval)
        ),
        output_record_rval_decls_format(Info, Stream, Rval,
            FirstIndent, LaterIndent, !N, !DeclSet, !IO)
    ;
        MemRef = heap_ref(BaseRval, _, OffsetRval),
        output_record_rval_decls_format(Info, Stream, BaseRval,
            FirstIndent, LaterIndent, !N, !DeclSet, !IO),
        output_record_rval_decls_format(Info, Stream, OffsetRval,
            FirstIndent, LaterIndent, !N, !DeclSet, !IO)
    ).

%----------------------------------------------------------------------------%
%
% Writing rvals.
%

output_rval(Info, Rval, Stream, !IO) :-
    (
        Rval = const(Const),
        output_rval_const(Info, Const, Stream, !IO)
    ;
        Rval = cast(Type, SubRval),
        TypeStr = llds_type_to_string(Type),
        io.write_string(Stream, "((", !IO),
        io.write_string(Stream, TypeStr, !IO),
        io.write_string(Stream, ") ", !IO),
        output_rval(Info, SubRval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Rval = unop(UnaryOp, SubRval),
        c_util.unary_prefix_op(UnaryOp, OpString),
        llds.unop_arg_type(UnaryOp, ArgType),
        io.write_string(Stream, OpString, !IO),
        io.write_string(Stream, "(", !IO),
        output_rval_as_type(Info, SubRval, ArgType, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Rval = binop(BinOp, SubRvalA, SubRvalB),
        output_rval_binop(Stream, Info, BinOp, SubRvalA, SubRvalB, !IO)
    ;
        Rval = mkword(ptag(PtagUInt8), SubRval),
        ( if
            SubRval = const(llconst_data_addr(DataId)),
            DataId = scalar_common_data_id(type_num(TypeNum), CellNum)
        then
            io.format(Stream, "MR_TAG_COMMON(%u, %d, %d)",
                [u8(PtagUInt8), i(TypeNum), i(CellNum)], !IO)
        else if
            SubRval = unop(mkbody, const(llconst_int(Body)))
        then
            io.format(Stream, "MR_tbmkword(%u, %d)",
                [u8(PtagUInt8), i(Body)], !IO)
        else
            io.format(Stream, "MR_tmkword(%u, ", [u8(PtagUInt8)], !IO),
            output_rval_as_type(Info, SubRval, lt_data_ptr, Stream, !IO),
            io.write_string(Stream, ")", !IO)
        )
    ;
        Rval = mkword_hole(ptag(PtagUInt8)),
        io.format(Stream, "MR_tmkword(%u, 0)", [u8(PtagUInt8)], !IO)
    ;
        Rval = lval(Lval),
        % If a field is used as an rval, then we need to use the
        % MR_const_field() macro or its variants, not the MR_field() macro
        % or its variants, to avoid warnings about discarding const.
        ( if Lval = field(MaybePtag, Rval, FieldNumRval) then
            (
                MaybePtag = yes(ptag(PtagUInt8)),
                io.format(Stream, "MR_ctfield(%u, ", [u8(PtagUInt8)], !IO)
            ;
                MaybePtag = no,
                io.write_string(Stream, "MR_const_mask_field(", !IO)
            ),
            output_rval(Info, Rval, Stream, !IO),
            io.write_string(Stream, ", ", !IO),
            ( if FieldNumRval = const(llconst_int(FieldNum)) then
                % Avoid emitting the (MR_Integer) cast.
                io.write_int(Stream, FieldNum, !IO)
            else
                output_rval(Info, FieldNumRval, Stream, !IO)
            ),
            io.write_string(Stream, ")", !IO)
        else
            output_lval(Info, Stream, Lval, !IO)
        )
    ;
        Rval = var(_),
        unexpected($file, $pred, "cannot output a var(_) expression in code")
    ;
        Rval = mem_addr(MemRef),
        (
            MemRef = stackvar_ref(SubRval),
            io.write_string(Stream, "&MR_sv(", !IO),
            % Don't clutter the output with unnecessary casts.
            ( if SubRval = const(llconst_int(SlotNum)) then
                io.write_int(Stream, SlotNum, !IO)
            else
                output_rval_as_type(Info, SubRval, lt_int(int_type_int),
                    Stream, !IO)
            ),
            io.write_string(Stream, ")", !IO)
        ;
            MemRef = framevar_ref(SubRval),
            io.write_string(Stream, "&MR_fv(", !IO),
            % Don't clutter the output with unnecessary casts.
            ( if SubRval = const(llconst_int(SlotNum)) then
                io.write_int(Stream, SlotNum, !IO)
            else
                output_rval_as_type(Info, SubRval, lt_int(int_type_int),
                    Stream, !IO)
            ),
            io.write_string(Stream, ")", !IO)
        ;
            MemRef = heap_ref(BaseRval, MaybePtag, FieldNumRval),
            (
                MaybePtag = yes(ptag(PtagUInt8)),
                io.format(Stream, "&MR_tfield(%u, ", [u8(PtagUInt8)], !IO)
            ;
                MaybePtag = no,
                io.write_string(Stream, "&MR_mask_field(", !IO)
            ),
            output_rval(Info, BaseRval, Stream, !IO),
            io.write_string(Stream, ", ", !IO),
            % Don't clutter the output with unnecessary casts.
            ( if FieldNumRval = const(llconst_int(FieldNum)) then
                io.write_int(Stream, FieldNum, !IO)
            else
                output_rval_as_type(Info, FieldNumRval, lt_int(int_type_int),
                    Stream, !IO)
            ),
            io.write_string(Stream, ")", !IO)
        )
    ).

:- pred output_rval_const(llds_out_info::in, rval_const::in,
    io.text_output_stream::in, io::di, io::uo) is det.

output_rval_const(Info, Const, Stream, !IO) :-
    (
        Const = llconst_true,
        io.write_string(Stream, "MR_TRUE", !IO)
    ;
        Const = llconst_false,
        io.write_string(Stream, "MR_FALSE", !IO)
    ;
        Const = llconst_int(N),
        c_util.output_int_as_c_expr(Stream, N, !IO)
    ;
        Const = llconst_uint(N),
        c_util.output_uint_as_c_expr(Stream, N, !IO)
    ;
        Const = llconst_int8(N),
        c_util.output_int8_as_c_expr(Stream, N, !IO)
    ;
        Const = llconst_uint8(N),
        c_util.output_uint8_as_c_expr(Stream, N, !IO)
    ;
        Const = llconst_int16(N),
        c_util.output_int16_as_c_expr(Stream, N, !IO)
    ;
        Const = llconst_uint16(N),
        c_util.output_uint16_as_c_expr(Stream, N, !IO)
    ;
        Const = llconst_int32(N),
        c_util.output_int32_as_c_expr(Stream, N, !IO)
    ;
        Const = llconst_uint32(N),
        c_util.output_uint32_as_c_expr(Stream, N, !IO)
    ;
        Const = llconst_int64(N),
        c_util.output_int64_as_c_expr(Stream, N, !IO)
    ;
        Const = llconst_uint64(N),
        c_util.output_uint64_as_c_expr(Stream, N, !IO)
    ;
        Const = llconst_foreign(Value, Type),
        io.write_char(Stream, '(', !IO),
        output_llds_type_cast(Stream, Type, !IO),
        io.write_string(Stream, Value, !IO),
        io.write_char(Stream, ')', !IO)
    ;
        Const = llconst_float(FloatVal),
        % The cast to (MR_Float) here lets the C compiler do arithmetic in
        % `float' rather than `double' if `MR_Float' is `float' not `double'.
        output_llds_type_cast(Stream, lt_float, !IO),
        c_util.output_float_literal(Stream, FloatVal, !IO)
    ;
        Const = llconst_string(String),
        io.write_string(Stream, "MR_string_const(", !IO),
        output_quoted_mmf_string_c(Stream, String, !IO),
        io.write_string(Stream, ", ", !IO),
        io.write_int(Stream, string.count_utf8_code_units(String), !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Const = llconst_multi_string(Strings),
        io.write_string(Stream, "MR_string_const(", !IO),
        output_quoted_multi_string_c(Stream, Strings, !IO),
        io.write_string(Stream, ", ", !IO),

        % The "+1" is for the NULL character.
        SumLengths = (func(S, L0) = L0 + string.count_utf8_code_units(S) + 1),
        Length = list.foldl(SumLengths, Strings, 0),
        io.write_int(Stream, Length, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Const = llconst_code_addr(CodeAddress),
        output_code_addr(Stream, CodeAddress, !IO)
    ;
        Const = llconst_data_addr(DataId),
        % Data addresses are all assumed to be of type `MR_Word *'; we need to
        % cast them here to avoid type errors. The offset is also in MR_Words.
        %
        % The tests for special cases below increase the runtime of the
        % compiler very slightly, but the use of shorter names reduces
        % the size of the generated C source file, which has a
        % considerably longer lifetime. In debugging grades, the
        % file size difference can be very substantial.
        ( if
            DataId = scalar_common_data_id(type_num(TypeNum), CellNum)
        then
            io.format(Stream, "MR_COMMON(%d, %d)",
                [i(TypeNum), i(CellNum)], !IO)
        else if
            DataId = rtti_data_id(RttiId),
            rtti_id_emits_type_ctor_info(RttiId, Ctor),
            Ctor = rtti_type_ctor(Module, Name, Arity),
            sym_name_doesnt_need_mangling(Module),
            name_doesnt_need_mangling(Name)
        then
            output_type_ctor_addr(Stream, Module, Name, Arity, !IO)
        else
            output_llds_type_cast(Stream, lt_data_ptr, !IO),
            output_data_id_addr(Info, Stream, DataId, !IO)
        )
    ;
        Const = llconst_data_addr_word_offset(DataId, Offset),
        io.write_string(Stream, "((", !IO),
        output_llds_type_cast(Stream, lt_data_ptr, !IO),
        output_data_id_addr(Info, Stream, DataId, !IO),
        io.write_string(Stream, ") + ", !IO),
        io.write_int(Stream, Offset, !IO),
        io.write_string(Stream, ")", !IO)
    ).

:- pred output_rval_binop(io.text_output_stream::in, llds_out_info::in,
    binary_op::in, rval::in, rval::in, io::di, io::uo) is det.

output_rval_binop(Stream, Info, Op, SubRvalA, SubRvalB, !IO) :-
    (
        Op = array_index(_),
        io.write_string(Stream, "(", !IO),
        output_rval_as_type(Info, SubRvalA, lt_data_ptr, Stream, !IO),
        io.write_string(Stream, ")[", !IO),
        output_rval_as_type(Info, SubRvalB, lt_int(int_type_int), Stream, !IO),
        io.write_string(Stream, "]", !IO)
    ;
        Op = string_unsafe_index_code_unit,
        io.write_string(Stream, "MR_nth_code_unit(", !IO),
        output_rval_as_type(Info, SubRvalA, lt_data_ptr, Stream, !IO),
        io.write_string(Stream, ", ", !IO),
        output_rval_as_type(Info, SubRvalB, lt_int(int_type_int), Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Op = pointer_equal_conservative,
        io.write_string(Stream, "(((MR_Word) ", !IO),
        output_rval(Info, SubRvalA, Stream, !IO),
        io.write_string(Stream, ") == ((MR_Word) ", !IO),
        output_rval(Info, SubRvalB, Stream, !IO),
        io.write_string(Stream, "))", !IO)
    ;
        Op = str_cmp(CmpOp),
        OpStr = cmp_op_c_operator(CmpOp),
        io.write_string(Stream, "(strcmp(", !IO),
        ( if SubRvalA = const(llconst_string(SubRvalAConst)) then
            output_rval_const(Info, llconst_string(SubRvalAConst), Stream, !IO)
        else
            io.write_string(Stream, "(char *) ", !IO),
            output_rval_as_type(Info, SubRvalA, lt_data_ptr, Stream, !IO)
        ),
        io.write_string(Stream, ", ", !IO),
        ( if SubRvalB = const(llconst_string(SubRvalBConst)) then
            output_rval_const(Info, llconst_string(SubRvalBConst), Stream, !IO)
        else
            io.write_string(Stream, "(char *) ", !IO),
            output_rval_as_type(Info, SubRvalB, lt_data_ptr, Stream, !IO)
        ),
        io.format(Stream, ") %s 0)", [s(OpStr)], !IO)
    ;
        (
            Op = float_cmp(CmpOp),
            OpStr = cmp_op_c_operator(CmpOp)
        ;
            Op = float_arith(ArithOp),
            OpStr = arith_op_c_operator(coerce(ArithOp))
        ),
        io.write_string(Stream, "(", !IO),
        output_rval_as_type(Info, SubRvalA, lt_float, Stream, !IO),
        io.format(Stream, " %s ", [s(OpStr)], !IO),
        output_rval_as_type(Info, SubRvalB, lt_float, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        (
            Op = int_as_uint_cmp(CmpOp)
        ;
            Op = in_range,
            CmpOp = lt
        ),
        OpStr = cmp_op_c_operator(coerce(CmpOp)),
        Uint = lt_int(int_type_uint),
        io.write_string(Stream, "(", !IO),
        output_rval_as_type(Info, SubRvalA, Uint, Stream, !IO),
        io.format(Stream, " %s ", [s(OpStr)], !IO),
        output_rval_as_type(Info, SubRvalB, Uint, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Op = int_arith(IntType, ArithOp),
        (
            ( ArithOp = ao_add, OpStr = "+"
            ; ArithOp = ao_sub, OpStr = "-"
            ; ArithOp = ao_mul, OpStr = "*"
            ),
            (
                (
                    IntType = int_type_int,
                    SignedType = "MR_Integer",
                    UnsignedType = "MR_Unsigned"
                ;
                    IntType = int_type_int8,
                    SignedType = "int8_t",
                    UnsignedType = "uint8_t"
                ;
                    IntType = int_type_int16,
                    SignedType = "int16_t",
                    UnsignedType = "uint16_t"
                ;
                    IntType = int_type_int32,
                    SignedType = "int32_t",
                    UnsignedType = "uint32_t"
                ;
                    IntType = int_type_int64,
                    SignedType = "int64_t",
                    UnsignedType = "uint64_t"
                ),
                Type = lt_int(IntType),
                % We used to handle X + (-C) (for constant C) specially, by
                % converting it to X - C, but we no longer do that since it
                % would overflow in the case where C == min_int.
                io.format(Stream, "(%s) ((%s) ",
                    [s(SignedType), s(UnsignedType)], !IO),
                output_rval_as_type(Info, SubRvalA, Type, Stream, !IO),
                io.format(Stream, " %s (%s) ",
                    [s(OpStr), s(UnsignedType)], !IO),
                output_rval_as_type(Info, SubRvalB, Type, Stream, !IO),
                io.write_string(Stream, ")", !IO)
            ;
                ( IntType = int_type_uint
                ; IntType = int_type_uint8
                ; IntType = int_type_uint16
                ; IntType = int_type_uint32
                ; IntType = int_type_uint64
                ),
                Type = lt_int(IntType),
                io.write_string(Stream, "(", !IO),
                output_rval_as_type(Info, SubRvalA, Type, Stream, !IO),
                io.format(Stream, " %s ", [s(OpStr)], !IO),
                output_rval_as_type(Info, SubRvalB, Type, Stream, !IO),
                io.write_string(Stream, ")", !IO)
            )
        ;
            ( ArithOp = ao_div, OpStr = "/"
            ; ArithOp = ao_rem, OpStr = "%"
            ),
            io.write_string(Stream, "(", !IO),
            output_rval_as_type(Info, SubRvalA, lt_int(IntType), Stream, !IO),
            io.format(Stream, " %s ", [s(OpStr)], !IO),
            output_rval_as_type(Info, SubRvalB, lt_int(IntType), Stream, !IO),
            io.write_string(Stream, ")", !IO)
        )
    ;
        Op = int_cmp(IntType, CmpOp),
        OpStr = cmp_op_c_operator(CmpOp),
        ( if
            % Special-case equality ops to avoid some unnecessary casts --
            % there is no difference between signed and unsigned equality,
            % so if both args are unsigned, we don't need to cast them to
            % MR_Integer.
            ( CmpOp = eq ; CmpOp = ne ),
            require_complete_switch [IntType]
            (
                ( IntType = int_type_int
                ; IntType = int_type_uint
                )
            ;
                % Don't apply this special case for sub-word-sized types,
                % to avoid having any differences in the rest of the word
                % convert an "equal" result to a "not equal" result.
                %
                % Don't apply this special case for 64-bit integer types,
                % since they may be boxed.
                ( IntType = int_type_int8
                ; IntType = int_type_uint8
                ; IntType = int_type_int16
                ; IntType = int_type_uint16
                ; IntType = int_type_int32
                ; IntType = int_type_uint32
                ; IntType = int_type_int64
                ; IntType = int_type_uint64
                ),
                fail
            ),
            llds.rval_type(SubRvalA, SubRvalAType),
            ( SubRvalAType = lt_word
            ; SubRvalAType = lt_int(int_type_uint)
            ),
            llds.rval_type(SubRvalB, SubRvalBType),
            ( SubRvalBType = lt_word
            ; SubRvalBType = lt_int(int_type_uint)
            )
        then
            io.write_string(Stream, "(", !IO),
            output_rval(Info, SubRvalA, Stream, !IO),
            io.format(Stream, " %s ", [s(OpStr)], !IO),
            output_rval(Info, SubRvalB, Stream, !IO),
            io.write_string(Stream, ")", !IO)
        else
            io.write_string(Stream, "(", !IO),
            output_rval_as_type(Info, SubRvalA, lt_int(IntType), Stream, !IO),
            io.format(Stream, " %s ", [s(OpStr)], !IO),
            output_rval_as_type(Info, SubRvalB, lt_int(IntType), Stream, !IO),
            io.write_string(Stream, ")", !IO)
        )
    ;
        ( Op = bitwise_and(IntType),    OpStr = "&"
        ; Op = bitwise_or(IntType),     OpStr = "|"
        ; Op = bitwise_xor(IntType),    OpStr = "^"
        ),
        io.write_string(Stream, "(", !IO),
        output_rval_as_type(Info, SubRvalA, lt_int(IntType), Stream, !IO),
        io.format(Stream, " %s ", [s(OpStr)], !IO),
        output_rval_as_type(Info, SubRvalB, lt_int(IntType), Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        ( Op = logical_and, OpStr = "&&"
        ; Op = logical_or,  OpStr = "||"
        ),
        io.write_string(Stream, "(", !IO),
        output_test_rval(Info, SubRvalA, Stream, !IO),
        io.format(Stream, " %s ", [s(OpStr)], !IO),
        output_test_rval(Info, SubRvalB, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        % The second operand of the shift operators always has type
        % `int' in C, but Mercury also allows it to be uint.
        ( Op = unchecked_left_shift(IntType, ShiftType),    OpStr = "<<"
        ; Op = unchecked_right_shift(IntType, ShiftType),   OpStr = ">>"
        ),
        io.write_string(Stream, "(", !IO),
        output_rval_as_type(Info, SubRvalA, lt_int(IntType), Stream, !IO),
        io.format(Stream, " %s ", [s(OpStr)], !IO),
        (
            ShiftType = shift_by_int
        ;
            ShiftType = shift_by_uint,
            io.write_string(Stream, "(int) ", !IO)
        ),
        output_rval_as_type(Info, SubRvalB, lt_int(int_type_int), Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Op = str_nzp,
        io.write_string(Stream, "MR_strcmp(", !IO),
        output_rval_as_type(Info, SubRvalA, lt_data_ptr, Stream, !IO),
        io.write_string(Stream, ", ", !IO),
        output_rval_as_type(Info, SubRvalB, lt_data_ptr, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        Op = offset_str_eq(Offset, MaybeSize),
        (
            MaybeSize = no_size,
            io.write_string(Stream, "MR_offset_streq(", !IO),
            io.write_int(Stream, Offset, !IO),
            io.write_string(Stream, ", ", !IO),
            output_rval_as_type(Info, SubRvalA, lt_string, Stream, !IO),
            io.write_string(Stream, ", ", !IO),
            output_rval_as_type(Info, SubRvalB, lt_string, Stream, !IO),
            io.write_string(Stream, ")", !IO)
        ;
            MaybeSize = size(Size),
            io.write_string(Stream, "MR_offset_strn_eq(", !IO),
            io.write_int(Stream, Offset, !IO),
            io.write_string(Stream, ", ", !IO),
            io.write_int(Stream, Size, !IO),
            io.write_string(Stream, ", ", !IO),
            output_rval_as_type(Info, SubRvalA, lt_string, Stream, !IO),
            io.write_string(Stream, ", ", !IO),
            output_rval_as_type(Info, SubRvalB, lt_string, Stream, !IO),
            io.write_string(Stream, ")", !IO)
        )
    ;
        Op = body,
        io.write_string(Stream, "MR_body(", !IO),
        output_rval_as_type(Info, SubRvalA, lt_int(int_type_int), Stream, !IO),
        io.write_string(Stream, ", ", !IO),
        output_rval_as_type(Info, SubRvalB, lt_int(int_type_int), Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ;
        ( Op = float_from_dword,  OpStr = "MR_float_from_dword"
        ; Op = int64_from_dword,  OpStr = "MR_int64_from_dword"
        ; Op = uint64_from_dword, OpStr = "MR_uint64_from_dword"
        ),
        io.write_string(Stream, OpStr, !IO),
        ( if is_aligned_dword_ptr(SubRvalA, SubRvalB, MemRef) then
            io.write_string(Stream, "_ptr(MR_dword_ptr(", !IO),
            output_rval(Info, mem_addr(MemRef), Stream, !IO),
            io.write_string(Stream, "))", !IO)
        else
            io.write_string(Stream, "(", !IO),
            output_rval(Info, SubRvalA, Stream, !IO),
            io.write_string(Stream, ", ", !IO),
            output_rval(Info, SubRvalB, Stream, !IO),
            io.write_string(Stream, ")", !IO)
        )
    ).

:- pred output_type_ctor_addr(io.text_output_stream::in,
    module_name::in, string::in, uint16::in, io::di, io::uo) is det.

output_type_ctor_addr(Stream, Module0, Name, Arity, !IO) :-
    ( if Module0 = unqualified("") then
        Module = mercury_public_builtin_module
    else
        Module = Module0
    ),
    % We don't need to mangle the module name, but we do need to convert it
    % to a C identifier in the standard fashion.
    ModuleStr = sym_name_mangle(Module),
    ( if Arity = 0u16 then
        ( if
            require_switch_arms_semidet [ModuleStr]
            (
                ModuleStr = "builtin",
                builtin_type_to_type_ctor_addr(Name, Macro)
            ;
                ModuleStr = "io",
                Name = "state",
                Macro = "MR_IO_CTOR_ADDR"
            ;
                ModuleStr = "bool",
                Name = "bool",
                Macro = "MR_BOOL_CTOR_ADDR"
            )
        then
            io.write_string(Stream, Macro, !IO)
        else
            io.format(Stream, "MR_CTOR0_ADDR(%s, %s)",
                [s(ModuleStr), s(Name)], !IO)
        )
    else if Arity = 1u16 then
        ( if
            require_switch_arms_semidet [Name]
            (
                Name = "list",
                ModuleStr = "list",
                Macro = "MR_LIST_CTOR_ADDR"
            ;
                Name = "type_info",
                ModuleStr = "private_builtin",
                Macro = "MR_TYPE_INFO_CTOR_ADDR"
            )
        then
            io.write_string(Stream, Macro, !IO)
        else
            io.format(Stream, "MR_CTOR1_ADDR(%s, %s)",
                [s(ModuleStr), s(Name)], !IO)
        )
    else
        io.format(Stream, "MR_CTOR_ADDR(%s, %s, %d)",
            [s(ModuleStr), s(Name), i(uint16.to_int(Arity))], !IO)
    ).

:- pred builtin_type_to_type_ctor_addr(string::in, string::out) is semidet.

builtin_type_to_type_ctor_addr(Name, Macro) :-
    ( Name = "int",         Macro = "MR_INT_CTOR_ADDR"
    ; Name = "int8",        Macro = "MR_INT8_CTOR_ADDR"
    ; Name = "int16",       Macro = "MR_INT16_CTOR_ADDR"
    ; Name = "int32",       Macro = "MR_INT32_CTOR_ADDR"
    ; Name = "int64",       Macro = "MR_INT64_CTOR_ADDR"
    ; Name = "uint",        Macro = "MR_UINT_CTOR_ADDR"
    ; Name = "uint8",       Macro = "MR_UINT8_CTOR_ADDR"
    ; Name = "uint16",      Macro = "MR_UINT16_CTOR_ADDR"
    ; Name = "uint32",      Macro = "MR_UINT32_CTOR_ADDR"
    ; Name = "uint64",      Macro = "MR_UINT64_CTOR_ADDR"
    ; Name = "float",       Macro = "MR_FLOAT_CTOR_ADDR"
    ; Name = "character",   Macro = "MR_CHAR_CTOR_ADDR"
    ; Name = "string",      Macro = "MR_STRING_CTOR_ADDR"
    ).

:- type dword_type
    --->    dword_float
    ;       dword_int64
    ;       dword_uint64.

output_rval_as_type(Info, Rval, DesiredType, Stream, !IO) :-
    llds.rval_type(Rval, ActualType),
    ( if llds_types_match(DesiredType, ActualType) then
        % No casting needed.
        output_rval(Info, Rval, Stream, !IO)
    else
        % We need to convert Rval to DesiredType first.
        % Conversions to/from float, int64 and uint64 must be treated
        % specially; for the others, we can just use a cast.
        ( if
            (
                DesiredType = lt_float,
                ConvMacro = "MR_word_to_float"
            ;
                DesiredType = lt_int(int_type_int64),
                ConvMacro = "MR_word_to_int64"
            ;
                DesiredType = lt_int(int_type_uint64),
                ConvMacro = "MR_word_to_uint64"
            )
        then
            io.write_string(Stream, ConvMacro, !IO),
            io.write_string(Stream, "(", !IO),
            output_rval(Info, Rval, Stream, !IO),
            io.write_string(Stream, ")", !IO)
        else if
            ( ActualType = lt_float,                DwordType = dword_float
            ; ActualType = lt_int(int_type_int64),  DwordType = dword_int64
            ; ActualType = lt_int(int_type_uint64), DwordType = dword_uint64
            )
        then
            % Note that we cannot switch on ActualType here, because
            % once control leaves the switch above, the compiler forgets
            % the fact that lt_int's argument can only be int_type_{u,}int64.
            (
                DwordType = dword_float,
                ( if DesiredType = lt_word then
                    output_float_rval_as_word(Info, Stream, Rval, !IO)
                else if DesiredType = lt_data_ptr then
                    output_float_rval_as_data_ptr(Info, Stream, Rval, !IO)
                else
                    unexpected($file, $pred, "type error")
                )
            ;
                DwordType = dword_int64,
                ( if DesiredType = lt_word then
                    output_int64_rval_as_word(Info, Stream, Rval, !IO)
                else if DesiredType = lt_data_ptr then
                    output_int64_rval_as_data_ptr(Info, Stream, Rval, !IO)
                else
                    unexpected($file, $pred, "type error")
                )
            ;
                DwordType = dword_uint64,
                ( if DesiredType = lt_word then
                    output_uint64_rval_as_word(Info, Stream, Rval, !IO)
                else if DesiredType = lt_data_ptr then
                    output_uint64_rval_as_data_ptr(Info, Stream, Rval, !IO)
                else
                    unexpected($file, $pred, "type error")
                )
            )
        else
            ( if
                Rval = const(llconst_int(N)),
                direct_field_int_constant(DesiredType) = yes
            then
                % The condition above increases the runtime of the compiler
                % very slightly. The elimination of the unnecessary casts
                % reduces the size of the generated C source file, which has
                % a considerably longer lifetime. In debugging grades,
                % the file size difference can be very substantial; it can be
                % in the range of megabytes.
                io.write_int(Stream, N, !IO)
            else
                % Cast value to desired type.
                io.write_string(Stream, "(", !IO),
                output_llds_type_cast(Stream, DesiredType, !IO),
                output_rval(Info, Rval, Stream, !IO),
                io.write_string(Stream, ")", !IO)
            )
        )
    ).

    % Output a float rval, converted to type `MR_Word *'.
    %
:- pred output_float_rval_as_data_ptr(llds_out_info::in,
    io.text_output_stream::in, rval::in, io::di, io::uo) is det.

output_float_rval_as_data_ptr(Stream, Info, Rval, !IO) :-
    output_float_rval(Stream, Info, Rval, yes, !IO).

    % Output a float rval, converted to type `MR_Word'.
    %
:- pred output_float_rval_as_word(llds_out_info::in, io.text_output_stream::in,
    rval::in, io::di, io::uo) is det.

output_float_rval_as_word(Info, Stream, Rval, !IO) :-
    output_float_rval(Info, Stream, Rval, no, !IO).

    % Output a float rval, converted to type `MR_Word' or `MR_Word *'.
    %
:- pred output_float_rval(llds_out_info::in, io.text_output_stream::in,
    rval::in, bool::in, io::di, io::uo) is det.

output_float_rval(Info, Stream, Rval, IsPtr, !IO) :-
    % For float constant expressions, if we are using boxed floats
    % and --static-ground-floats is enabled, we just refer to the static const
    % which we declared earlier.
    UnboxFloat = Info ^ lout_unboxed_float,
    StaticGroundFloats = Info ^ lout_static_ground_floats,
    ( if
        UnboxFloat = no,
        StaticGroundFloats = use_static_ground_floats,
        float_const_expr_name(Rval, FloatName)
    then
        (
            IsPtr = yes,
            Cast = lt_data_ptr
        ;
            IsPtr = no,
            Cast = lt_word
        ),
        output_llds_type_cast(Stream, Cast, !IO),
        io.write_string(Stream, "&mercury_float_const_", !IO),
        io.write_string(Stream, FloatName, !IO)
    else
        (
            IsPtr = yes,
            output_llds_type_cast(Stream, lt_data_ptr, !IO)
        ;
            IsPtr = no
        ),
        io.write_string(Stream, "MR_float_to_word(", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ).

    % Output a int64 rval, converted to type `MR_Word *'
    %
:- pred output_int64_rval_as_data_ptr(llds_out_info::in,
    io.text_output_stream::in, rval::in, io::di, io::uo) is det.

output_int64_rval_as_data_ptr(Info, Stream, Rval, !IO) :-
    output_int64_rval(Info, Stream, Rval, yes, !IO).

    % Output a int64 rval, converted to type `MR_Word'
    %
:- pred output_int64_rval_as_word(llds_out_info::in, io.text_output_stream::in,
    rval::in, io::di, io::uo) is det.

output_int64_rval_as_word(Info, Stream, Rval, !IO) :-
    output_int64_rval(Info, Stream, Rval, no, !IO).

    % Output a int64 rval, converted to type `MR_Word' or `MR_Word *'
    %
:- pred output_int64_rval(llds_out_info::in, io.text_output_stream::in,
    rval::in, bool::in, io::di, io::uo) is det.

output_int64_rval(Info, Stream, Rval, IsPtr, !IO) :-
    % For int64 constants, if we are using boxed 64-bit integers and
    % --static-ground-int64s is enabled, we just refer to the static const
    % which we declared earlier.
    UnboxInt64s = Info ^ lout_unboxed_int64s,
    StaticGroundInt64s = Info ^ lout_static_ground_int64s,
    ( if
        UnboxInt64s = no,
        StaticGroundInt64s = use_static_ground_int64s,
        int64_const_expr_name(Rval, Int64Name)
    then
        (
            IsPtr = yes,
            Cast = lt_data_ptr
        ;
            IsPtr = no,
            Cast = lt_word
        ),
        output_llds_type_cast(Stream, Cast, !IO),
        io.write_string(Stream, "&mercury_int64_const_", !IO),
        io.write_string(Stream, Int64Name, !IO)
    else
        (
            IsPtr = yes,
            output_llds_type_cast(Stream, lt_data_ptr, !IO)
        ;
            IsPtr = no
        ),
        io.write_string(Stream, "MR_int64_to_word(", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ).

    % Output a uint64 rval, converted to type `MR_Word *'.
    %
:- pred output_uint64_rval_as_data_ptr(llds_out_info::in,
    io.text_output_stream::in, rval::in, io::di, io::uo) is det.

output_uint64_rval_as_data_ptr(Info, Stream, Rval, !IO) :-
    output_uint64_rval(Info, Stream, Rval, yes, !IO).

    % Output a uint64 rval, converted to type `MR_Word'.
    %
:- pred output_uint64_rval_as_word(llds_out_info::in,
    io.text_output_stream::in, rval::in, io::di, io::uo) is det.

output_uint64_rval_as_word(Info, Stream, Rval, !IO) :-
    output_uint64_rval(Info, Stream, Rval, no, !IO).

    % Output a uint64 rval, converted to type `MR_Word' or `MR_Word *'.
    %
:- pred output_uint64_rval(llds_out_info::in, io.text_output_stream::in,
    rval::in, bool::in, io::di, io::uo) is det.

output_uint64_rval(Info, Stream, Rval, IsPtr, !IO) :-
    % For uint64 constants, if we are using boxed 64-bit integers and
    % --static-ground-int64s is enabled, we just refer to the static const
    % which we declared earlier.
    UnboxInt64s = Info ^ lout_unboxed_int64s,
    StaticGroundInt64s = Info ^ lout_static_ground_int64s,
    ( if
        UnboxInt64s = no,
        StaticGroundInt64s = use_static_ground_int64s,
        uint64_const_expr_name(Rval, UInt64Name)
    then
        (
            IsPtr = yes,
            Cast = lt_data_ptr
        ;
            IsPtr = no,
            Cast = lt_word
        ),
        output_llds_type_cast(Stream, Cast, !IO),
        io.write_string(Stream, "&mercury_uint64_const_", !IO),
        io.write_string(Stream, UInt64Name, !IO)
    else
        (
            IsPtr = yes,
            output_llds_type_cast(Stream, lt_data_ptr, !IO)
        ;
            IsPtr = no
        ),
        io.write_string(Stream, "MR_uint64_to_word(", !IO),
        output_rval(Info, Rval, Stream, !IO),
        io.write_string(Stream, ")", !IO)
    ).

:- pred is_aligned_dword_ptr(rval::in, rval::in, mem_ref::out) is semidet.

is_aligned_dword_ptr(lval(LvalA), lval(LvalB), MemRef) :-
    (
        LvalA = stackvar(N),
        LvalB = stackvar(N + 1),
        % Double-width variables on the det stack should have been aligned
        % by the allocator. In a downwards-growing stack the higher slot
        % number has the lower address.
        MemRef = stackvar_ref(const(llconst_int(N + 1)))
    ;
        LvalA = field(_MaybePtag, _Address, _Offset),
        % We cannot guarantee that the Address is dword aligned.
        fail
    ).

:- type maybe_negated_test
    --->    plain_test
    ;       negated_test.

output_test_rval(Info, Test, Stream, !IO) :-
    do_output_test_rval(Stream, Info, plain_test, Test, !IO).

:- pred do_output_test_rval(io.text_output_stream::in, llds_out_info::in,
    maybe_negated_test::in, rval::in, io::di, io::uo) is det.

do_output_test_rval(Stream, Info, MaybeNegated, TestRval, !IO) :-
    (
        TestRval = unop(Unop, SubRvalA),
        ( if Unop = logical_not then
            ( MaybeNegated = plain_test,    SubNegated = negated_test
            ; MaybeNegated = negated_test,  SubNegated = plain_test
            ),
            do_output_test_rval(Stream, Info, SubNegated, SubRvalA, !IO)
        else
            do_output_test_rval_base(Stream, Info, MaybeNegated,
                TestRval, !IO)
        )
    ;
        TestRval = binop(Binop, SubRvalA, SubRvalB),
        (
            Binop = logical_and,
            ( if
                is_ptag_test(SubRvalA, VarRval, Ptag, no),
                is_remote_stag_test(SubRvalB, VarRval, Ptag, Sectag)
            then
                ( MaybeNegated = plain_test,    Macro = "MR_RTAGS_TEST"
                ; MaybeNegated = negated_test,  Macro = "MR_RTAGS_TESTR"
                ),
                io.write_string(Stream, Macro, !IO),
                io.write_string(Stream, "(", !IO),
                output_rval(Info, VarRval, Stream, !IO),
                io.write_string(Stream, ",", !IO),
                write_ptag(Stream, Ptag, !IO),
                io.write_string(Stream, ",", !IO),
                io.write_uint(Stream, Sectag, !IO),
                io.write_string(Stream, ")", !IO)
            else
                do_output_test_rval_base(Stream, Info, MaybeNegated,
                    TestRval, !IO)
            )
        ;
            Binop = int_cmp(IntType, CmpOp),
            ( CmpOp = eq, Negated0 = no
            ; CmpOp = ne, Negated0 = yes
            ),
            ( if
                args_are_of_ptag_test(SubRvalA, SubRvalB, VarRval, Ptag)
            then
                ( MaybeNegated = plain_test,    Negated = Negated0
                ; MaybeNegated = negated_test,  Negated = bool.not(Negated0)
                ),
                ( Negated = no,     Macro = "MR_PTAG_TEST"
                ; Negated = yes,    Macro = "MR_PTAG_TESTR"
                ),
                io.write_string(Stream, Macro, !IO),
                io.write_string(Stream, "(", !IO),
                output_rval(Info, VarRval, Stream, !IO),
                io.write_string(Stream, ",", !IO),
                write_ptag(Stream, Ptag, !IO),
                io.write_string(Stream, ")", !IO)
            else if
                SubRvalB = mkword(Ptag, unop(mkbody, const(ConstB))),
                ConstB = llconst_int(SectagInt),
                uint.from_int(SectagInt, Sectag)
            then
                VarRval = SubRvalA,
                ( MaybeNegated = plain_test,    Negated = Negated0
                ; MaybeNegated = negated_test,  Negated = bool.not(Negated0)
                ),
                ( Negated = no,     Macro = "MR_LTAGS_TEST"
                ; Negated = yes,    Macro = "MR_LTAGS_TESTR"
                ),
                io.write_string(Stream, Macro, !IO),
                io.write_string(Stream, "(", !IO),
                output_rval(Info, VarRval, Stream, !IO),
                io.write_string(Stream, ",", !IO),
                write_ptag(Stream, Ptag, !IO),
                io.write_string(Stream, ",", !IO),
                io.write_uint(Stream, Sectag, !IO),
                io.write_string(Stream, ")", !IO)
            else if
                SubRvalB = const(llconst_int(ConstB)),
                IntType = int_type_int
            then
                ( MaybeNegated = plain_test,    Negated = Negated0
                ; MaybeNegated = negated_test,  Negated = bool.not(Negated0)
                ),
                ( Negated = no,     Macro = "MR_INT_EQ"
                ; Negated = yes,    Macro = "MR_INT_NE"
                ),
                io.write_string(Stream, Macro, !IO),
                io.write_string(Stream, "(", !IO),
                output_rval(Info, SubRvalA, Stream, !IO),
                io.write_string(Stream, ",", !IO),
                io.write_int(Stream, ConstB, !IO),
                io.write_string(Stream, ")", !IO)
            else
                do_output_test_rval_base(Stream, Info, MaybeNegated,
                    TestRval, !IO)
            )
        ;
            Binop = int_cmp(IntType, CmpOp),
            ( CmpOp = lt, PosM = "MR_INT_LT", NegM = "MR_INT_GE"
            ; CmpOp = gt, PosM = "MR_INT_GT", NegM = "MR_INT_LT"
            ; CmpOp = le, PosM = "MR_INT_LE", NegM = "MR_INT_GT"
            ; CmpOp = ge, PosM = "MR_INT_GE", NegM = "MR_INT_LT"
            ),
            ( if
                SubRvalB = const(llconst_int(ConstB)),
                IntType = int_type_int
            then
                ( MaybeNegated = plain_test,    Macro = PosM
                ; MaybeNegated = negated_test,  Macro = NegM
                ),
                io.write_string(Stream, Macro, !IO),
                io.write_string(Stream, "(", !IO),
                output_rval(Info, SubRvalA, Stream, !IO),
                io.write_string(Stream, ",", !IO),
                io.write_int(Stream, ConstB, !IO),
                io.write_string(Stream, ")", !IO)
            else
                do_output_test_rval_base(Stream, Info, MaybeNegated,
                    TestRval, !IO)
            )
        ;
            ( Binop = int_arith(_, _)
            ; Binop = float_arith(_)
            ; Binop = int_as_uint_cmp(_)
            ; Binop = in_range
            ; Binop = float_cmp(_)
            ; Binop = str_cmp(_)
            ; Binop = str_nzp
            ; Binop = offset_str_eq(_, _)
            ; Binop = unchecked_left_shift(_, _)
            ; Binop = unchecked_right_shift(_, _)
            ; Binop = bitwise_and(_)
            ; Binop = bitwise_or(_)
            ; Binop = bitwise_xor(_)
            ; Binop = logical_or
            ; Binop = body
            ; Binop = array_index(_)
            ; Binop = string_unsafe_index_code_unit
            ; Binop = float_from_dword
            ; Binop = int64_from_dword
            ; Binop = uint64_from_dword
            ; Binop = pointer_equal_conservative
            ),
            do_output_test_rval_base(Stream, Info, MaybeNegated, TestRval, !IO)
        )
    ;
        ( TestRval = lval(_)
        ; TestRval = var(_)
        ; TestRval = mkword(_, _)
        ; TestRval = mkword_hole(_)
        ; TestRval = const(_)
        ; TestRval = cast(_, _)
        ; TestRval = mem_addr(_)
        ),
        do_output_test_rval_base(Stream, Info, MaybeNegated, TestRval, !IO)
    ).

:- pred do_output_test_rval_base(io.text_output_stream::in, llds_out_info::in,
    maybe_negated_test::in, rval::in, io::di, io::uo) is det.

do_output_test_rval_base(Stream, Info, MaybeNegated, Test0, !IO) :-
    (
        MaybeNegated = plain_test,
        Test = Test0
    ;
        MaybeNegated = negated_test,
        Test = unop(logical_not, Test0)
    ),
    output_rval_as_type(Info, Test, lt_bool, Stream, !IO).

:- pred is_ptag_test(rval::in, rval::out, ptag::out, bool::out) is semidet.

is_ptag_test(TestRval, VarRval, Ptag, Negated) :-
    TestRval = binop(BinOp, SubRvalA, SubRvalB),
    BinOp = int_cmp(_IntType, CmpOp),
    args_are_of_ptag_test(SubRvalA, SubRvalB, VarRval, Ptag),
    (
        CmpOp = eq,
        Negated = no
    ;
        CmpOp = ne,
        Negated = yes
    ).

:- pred args_are_of_ptag_test(rval::in, rval::in, rval::out, ptag::out)
    is semidet.

args_are_of_ptag_test(SubRvalA, SubRvalB, VarRval, Ptag) :-
    SubRvalA = unop(tag, VarRval),
    SubRvalB = const(llconst_int(PtagInt)),
    uint8.from_int(PtagInt, PtagUint8),
    Ptag = ptag(PtagUint8).

:- pred is_remote_stag_test(rval::in, rval::in, ptag::in, uint::out)
    is semidet.

is_remote_stag_test(Test, VarRval, Ptag, Sectag) :-
    Test = binop(int_cmp(int_type_int, eq), Left, Right),
    Left = lval(field(yes(Ptag), VarRval, Zero)),
    Zero = const(llconst_int(0)),
    Right = const(llconst_int(SectagInt)),
    uint.from_int(SectagInt, Sectag).

output_ptag(Stream, ptag(PtagUInt8), !IO) :-
    io.format(Stream, "MR_mktag(%u)", [u8(PtagUInt8)], !IO).

write_ptag(Stream, ptag(PtagUInt8), !IO) :-
    io.write_uint8(Stream, PtagUInt8, !IO).

direct_field_int_constant(LLDSType) = DirectFieldIntConstant :-
    (
        ( LLDSType = lt_bool
        ; LLDSType = lt_float
        ; LLDSType = lt_string
        ; LLDSType = lt_data_ptr
        ; LLDSType = lt_code_ptr
        ; LLDSType = lt_word
        ),
        DirectFieldIntConstant = no
    ;
        LLDSType = lt_int_least(_),
        DirectFieldIntConstant = yes
    ;
        LLDSType = lt_int(IntType),
        (
            ( IntType = int_type_int
            ; IntType = int_type_uint
            ; IntType = int_type_int8
            ; IntType = int_type_uint8
            ; IntType = int_type_int16
            ; IntType = int_type_uint16
            ; IntType = int_type_int32
            ; IntType = int_type_uint32
            ),
            DirectFieldIntConstant = yes
        ;
            ( IntType = int_type_int64
            ; IntType = int_type_uint64
            ),
            DirectFieldIntConstant = no
        )
    ).

%----------------------------------------------------------------------------%
%
% Compute the names of the global variables that hold floating point constants.
%

float_const_expr_name(Expr, Name) :-
    ( if Expr = const(llconst_float(Float)) then
        float_literal_name(Float, Name)
    else if Expr = binop(Op, Arg1, Arg2) then
        float_const_binop_expr_name(Op, Arg1, Arg2, Name)
    else
        fail
    ).

    % Given a binop rval, succeed iff that rval is a floating point constant
    % expression; if so, return a name for that rval that is suitable for use
    % in a C identifier. Different rvals must be given different names.
    %
:- pred float_const_binop_expr_name(binary_op::in, rval::in, rval::in,
    string::out) is semidet.

float_const_binop_expr_name(Op, Arg1, Arg2, Name) :-
    Op = float_arith(ArithOp),
    float_arith_op_name(ArithOp, OpName),
    float_const_expr_name(Arg1, Arg1Name),
    float_const_expr_name(Arg2, Arg2Name),
    % We use prefix notation (operator, argument, argument) rather than infix,
    % to ensure that different rvals get different names.
    Name = OpName ++ "_" ++ Arg1Name ++ "_" ++ Arg2Name.

    % Given an rval which is a floating point literal, return a name for that
    % rval that is suitable for use as a suffix of a C identifier.
    % Different rvals must be given different names.
    %
:- pred float_literal_name(float::in, string::out) is det.

float_literal_name(Float, FloatName) :-
    % The name of the variable is based on the value of the float const, with
    % "pt" instead of ".", "plus" instead of "+", and "neg" instead of "-".
    FloatName0 = c_util.make_float_literal(Float),
    string.replace_all(FloatName0, ".", "pt", FloatName1),
    string.replace_all(FloatName1, "+", "plus", FloatName2),
    string.replace_all(FloatName2, "-", "neg", FloatName).

    % Succeed iff the binary operator is an operator whose return
    % type is float; bind the output string to a name for that operator
    % that is suitable for use in a C identifier
    %
:- pred float_arith_op_name(float_arith_op::in, string::out) is det.

float_arith_op_name(ao_add, "fadd").
float_arith_op_name(ao_sub, "fsub").
float_arith_op_name(ao_mul, "fmul").
float_arith_op_name(ao_div, "fdiv").

%----------------------------------------------------------------------------%

    % Given an rval which is a signed 64-bit integer literal, return a name for
    % that rval that is suitable for use as a suffix of a C identifier.
    % Different rvals must be given different names.
    %
:- pred int64_literal_name(int64::in, string::out) is det.

int64_literal_name(Int64, Int64Name) :-
    Int64Name0 = int64_to_string(Int64),
    string.replace_all(Int64Name0, "-", "neg", Int64Name).

    % Given an rval which is an unsigned 64-bit integer literal, return a name
    % for that rval that is suitable for use as a suffix of a C identifier.
    % Different rvals must be given different names.
    %
:- pred uint64_literal_name(uint64::in, string::out) is det.

uint64_literal_name(UInt64, UInt64Name) :-
    UInt64Name = uint64_to_string(UInt64).

int64_const_expr_name(Expr, Name) :-
    Expr = const(llconst_int64(Int64)),
    int64_literal_name(Int64, Name).

uint64_const_expr_name(Expr, Name) :-
    Expr = const(llconst_uint64(UInt64)),
    uint64_literal_name(UInt64, Name).

%----------------------------------------------------------------------------%
%
% Declare the names of global variables.
%

output_record_data_id_decls(Info, Stream, DataId, !DeclSet, !IO) :-
    output_record_data_id_decls_format(Info, Stream, DataId, "", "",
        0, _, !DeclSet, !IO).

output_record_data_id_decls_format(Info, Stream, DataId,
        FirstIndent, LaterIndent, !N, !DeclSet, !IO) :-
    (
        ( DataId = scalar_common_data_id(_, _)
        ; DataId = vector_common_data_id(_, _)
        ; DataId = layout_slot_id(_, _)
        )
        % These are always declared at the top of the generated C source file.
    ;
        DataId = proc_tabling_data_id(_, _)
        % These are always defined (and therefore declared) before being used.
    ;
        DataId = rtti_data_id(RttiId),
        DeclId = decl_rtti_id(RttiId),
        ( if decl_set_insert_new(DeclId, !DeclSet) then
            output_indent(Stream, FirstIndent, LaterIndent, !.N, !IO),
            !:N = !.N + 1,
            output_rtti_id_storage_type_name_no_decl(Info, Stream, RttiId,
                no, !IO),
            io.write_string(Stream, ";\n", !IO)
        else
            true
        )
    ;
        DataId = layout_id(LayoutName),
        DeclId = decl_layout_id(LayoutName),
        ( if decl_set_insert_new(DeclId, !DeclSet) then
            output_indent(Stream, FirstIndent, LaterIndent, !.N, !IO),
            !:N = !.N + 1,
            output_layout_name_storage_type_name(Stream, LayoutName,
                not_being_defined, !IO),
            io.write_string(Stream, ";\n", !IO)
        else
            true
        )
    ).

%----------------------------------------------------------------------------%
%
% Output references to global variables.
%

output_data_id(Info, Stream, DataId, !IO) :-
    (
        DataId = rtti_data_id(RttiId),
        output_rtti_id(Stream, RttiId, !IO)
    ;
        DataId = proc_tabling_data_id(ProcLabel, TablingId),
        io.write_string(Stream,
            tabling_struct_data_addr_string(ProcLabel, TablingId), !IO)
    ;
        DataId = scalar_common_data_id(TypeNum, CellNum),
        output_common_scalar_cell_array_name(Stream, TypeNum, !IO),
        io.format(Stream, "[%d]", [i(CellNum)], !IO)
    ;
        DataId = vector_common_data_id(TypeNum, CellNum),
        output_common_vector_cell_array_name(Stream, TypeNum, CellNum, !IO)
    ;
        DataId = layout_id(LayoutName),
        output_layout_name(Stream, LayoutName, !IO)
    ;
        DataId = layout_slot_id(Kind, PredProcId),
        Kind = table_io_entry_id,
        TableIoEntryMap = Info ^ lout_table_io_entry_map,
        map.lookup(TableIoEntryMap, PredProcId, LayoutSlotName),
        MangledModuleName = Info ^ lout_mangled_module_name,
        output_layout_slot_id(Stream, use_layout_macro, MangledModuleName,
            LayoutSlotName, !IO)
    ).

output_data_id_addr(Info, Stream, DataId, !IO) :-
    io.write_string(Stream, "&", !IO),
    output_data_id(Info, Stream, DataId, !IO).

output_common_scalar_cell_array_name(Stream, type_num(TypeNum), !IO) :-
    io.format(Stream, "%s%d",
        [s(mercury_scalar_common_array_prefix), i(TypeNum)], !IO).

output_common_vector_cell_array_name(Stream, type_num(TypeNum),
        CellNum, !IO) :-
    io.format(Stream, "%s%d_%d",
        [s(mercury_vector_common_array_prefix), i(TypeNum), i(CellNum)], !IO).

%---------------------------------------------------------------------------%
:- end_module ll_backend.llds_out.llds_out_data.
%---------------------------------------------------------------------------%
