mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-13 12:53:53 +00:00
compiler/builtin_ops.m:
Recognize all of builtin_{int,uint}{,8,16,32,64}_{lt,gt} builtins.
Only two of these exist now, but the rest must be recognized as builtins
before their declarations can be added to library/private_builtin.m
without definition.
compiler/options.m:
Add an option name as a way to test whether the installed compiler
has this change to builtin_ops.m. The first change that needs the
new builtins will modify configure.ac to require the installed compiler
to support this option name.
502 lines
19 KiB
Mathematica
502 lines
19 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 1999-2001, 2003-2006, 2009-2011 The University of Melbourne.
|
|
% Copyright (C) 2014-2018 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: builtin_ops.m -- defines the builtin operator types.
|
|
% Main author: fjh.
|
|
%
|
|
% This module defines various types which enumerate the different builtin
|
|
% operators. Several of the different back-ends -- the bytecode back-end,
|
|
% the LLDS, and the MLDS -- all use the same set of builtin operators.
|
|
% These operators are defined here.
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module backend_libs.builtin_ops.
|
|
:- interface.
|
|
|
|
:- import_module hlds.
|
|
:- import_module hlds.hlds_pred.
|
|
:- import_module mdbcomp.
|
|
:- import_module mdbcomp.sym_name.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.prog_data.
|
|
|
|
:- import_module list.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type unary_op
|
|
---> tag
|
|
; strip_tag
|
|
; mkbody
|
|
; unmkbody
|
|
; bitwise_complement(int_type)
|
|
; logical_not
|
|
; hash_string
|
|
; hash_string2
|
|
; hash_string3
|
|
; hash_string4
|
|
; hash_string5
|
|
; hash_string6
|
|
; dword_float_get_word0
|
|
; dword_float_get_word1
|
|
; dword_int64_get_word0
|
|
; dword_int64_get_word1
|
|
; dword_uint64_get_word0
|
|
; dword_uint64_get_word1.
|
|
|
|
:- type binary_op
|
|
---> int_add(int_type)
|
|
; int_sub(int_type)
|
|
; int_mul(int_type)
|
|
; int_div(int_type) % Assumed to truncate toward zero.
|
|
; int_mod(int_type) % Remainder (w.r.t. truncating integer division).
|
|
% XXX `mod' should be renamed `rem'
|
|
; unchecked_left_shift(int_type)
|
|
; unchecked_right_shift(int_type)
|
|
; bitwise_and(int_type)
|
|
; bitwise_or(int_type)
|
|
; bitwise_xor(int_type)
|
|
; logical_and
|
|
; logical_or
|
|
% The following type are primarily used with integers, but also
|
|
% with characters and enumerations.
|
|
% XXX the latter two uses are not covered by int_type, for now we
|
|
% use the convention that they should use `int_type_int'.
|
|
; eq(int_type) % ==
|
|
; ne(int_type) % !=
|
|
; body
|
|
; array_index(array_elem_type)
|
|
; string_unsafe_index_code_unit
|
|
; str_eq % string comparisons
|
|
; str_ne
|
|
; str_lt
|
|
; str_gt
|
|
; str_le
|
|
; str_ge
|
|
; str_cmp % returns -ve, 0, or +ve
|
|
|
|
; offset_str_eq(int)
|
|
% This op is not recognized in user-written code; it is only
|
|
% generated by the compiler when implementing string switches
|
|
% via tries. binop(offset_str_eq(N), SA, SB) is used when the first
|
|
% N code units of two strings, SA and SB, are already known
|
|
% to be equal, and it tests whether their remaining code units
|
|
% are equal as well. It will execute "strcmp(SA+N, SB+N) == 0"
|
|
% or equivalent code on backends which support this, and code
|
|
% equivalent to "strcmp(SA, SB) == 0" on backends which don't.
|
|
|
|
; int_lt(int_type) % signed integer comparisons
|
|
; int_gt(int_type)
|
|
; int_le(int_type)
|
|
; int_ge(int_type)
|
|
|
|
; unsigned_le % unsigned integer comparison (for signed values)
|
|
% Note that the arguments to `unsigned_le' are just ordinary
|
|
% (signed) Mercury ints, but it does the comparison as
|
|
% if they were first cast to an unsigned type, so e.g.
|
|
% binary(unsigned_le, int_const(1), int_const(-1)) returns true,
|
|
% since (MR_Unsigned) 1 <= (MR_Unsigned) -1.
|
|
|
|
; float_plus % XXX the integer versions use different names.
|
|
; float_minus % E.g add instead of plus etc.
|
|
; float_times
|
|
; float_divide
|
|
; float_eq
|
|
; float_ne
|
|
; float_lt
|
|
; float_gt
|
|
; float_le
|
|
; float_ge
|
|
; float_from_dword
|
|
; int64_from_dword
|
|
; uint64_from_dword
|
|
|
|
; pointer_equal_conservative
|
|
|
|
; compound_eq
|
|
; compound_lt.
|
|
% Comparisons on values of non-atomic types. This is likely to be
|
|
% supported only on very high-level back-ends.
|
|
|
|
% For the MLDS back-end, we need to know the element type for each
|
|
% array_index operation.
|
|
%
|
|
% Currently array index operations are only generated in limited
|
|
% circumstances. Using a simple representation for them here,
|
|
% rather than just putting the MLDS type here, avoids the need
|
|
% for this module to depend on back-end specific stuff like MLDS types.
|
|
:- type array_elem_type
|
|
---> array_elem_scalar(scalar_array_elem_type)
|
|
; array_elem_struct(list(scalar_array_elem_type)).
|
|
|
|
:- type scalar_array_elem_type
|
|
---> scalar_elem_string % ml_string_type
|
|
; scalar_elem_int % mlds_native_int_type
|
|
; scalar_elem_generic. % mlds_generic_type
|
|
|
|
% test_if_builtin(ModuleName, PredName, ProcId, Args):
|
|
%
|
|
% Given a module name, a predicate name, a proc_id and a list of the
|
|
% arguments, find out if that procedure of that predicate is an inline
|
|
% builtin.
|
|
%
|
|
:- pred test_if_builtin(module_name::in, string::in, proc_id::in,
|
|
list(T)::in) is semidet.
|
|
|
|
% translate_builtin(ModuleName, PredName, ProcId, Args, Code):
|
|
%
|
|
% This predicate should be invoked only in cases where
|
|
% test_if_builtin(ModuleName, PredName, ProcId, Args) has succeeded.
|
|
%
|
|
% In such cases, it returns an abstract representation of the code
|
|
% that can be used to evaluate that call, which will be either
|
|
% an assignment (if the builtin is det) or a test (if the builtin
|
|
% is semidet).
|
|
%
|
|
% There are some further guarantees on the form of the expressions
|
|
% in the code returned, expressed in the form of the insts below.
|
|
% (bytecode_gen.m depends on these guarantees.)
|
|
%
|
|
:- pred translate_builtin(module_name::in, string::in, proc_id::in,
|
|
list(T)::in, simple_code(T)::out(simple_code)) is det.
|
|
|
|
:- type simple_code(T)
|
|
---> assign(T, simple_expr(T))
|
|
; ref_assign(T, T)
|
|
; test(simple_expr(T))
|
|
; noop(list(T)).
|
|
|
|
:- type simple_expr(T)
|
|
---> leaf(T)
|
|
; int_const(int)
|
|
; uint_const(uint)
|
|
; int8_const(int8)
|
|
; uint8_const(uint8)
|
|
; int16_const(int16)
|
|
; uint16_const(uint16)
|
|
; int32_const(int32)
|
|
; uint32_const(uint32)
|
|
; int64_const(int64)
|
|
; uint64_const(uint64)
|
|
; float_const(float)
|
|
; unary(unary_op, simple_expr(T))
|
|
; binary(binary_op, simple_expr(T), simple_expr(T)).
|
|
|
|
% Each test expression returned is guaranteed to be either a unary
|
|
% or binary operator, applied to arguments that are either variables
|
|
% (from the argument list) or constants.
|
|
%
|
|
% Each to be assigned expression is guaranteed to be either in a form
|
|
% acceptable for a test rval, or in the form of a variable.
|
|
|
|
:- inst simple_code for simple_code/1
|
|
---> assign(ground, simple_assign_expr)
|
|
; ref_assign(ground, ground)
|
|
; test(simple_test_expr)
|
|
; noop(ground).
|
|
|
|
:- inst simple_assign_expr for simple_expr/1
|
|
---> unary(ground, simple_arg_expr)
|
|
; binary(ground, simple_arg_expr, simple_arg_expr)
|
|
; leaf(ground).
|
|
|
|
:- inst simple_test_expr for simple_expr/1
|
|
---> unary(ground, simple_arg_expr)
|
|
; binary(ground, simple_arg_expr, simple_arg_expr).
|
|
|
|
:- inst simple_arg_expr for simple_expr/1
|
|
---> leaf(ground)
|
|
; int_const(ground)
|
|
; uint_const(ground)
|
|
; int8_const(ground)
|
|
; uint8_const(ground)
|
|
; int16_const(ground)
|
|
; uint16_const(ground)
|
|
; int32_const(ground)
|
|
; uint32_const(ground)
|
|
; int64_const(ground)
|
|
; uint64_const(ground)
|
|
; float_const(ground).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module mdbcomp.builtin_modules.
|
|
|
|
:- import_module require.
|
|
:- import_module string.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
test_if_builtin(FullyQualifiedModule, PredName, ProcId, Args) :-
|
|
is_std_lib_module_name(FullyQualifiedModule, ModuleName),
|
|
proc_id_to_int(ProcId, ProcNum),
|
|
builtin_translation(ModuleName, PredName, ProcNum, Args, _Code).
|
|
|
|
translate_builtin(FullyQualifiedModule, PredName, ProcId, Args, Code) :-
|
|
( if
|
|
is_std_lib_module_name(FullyQualifiedModule, ModuleName),
|
|
proc_id_to_int(ProcId, ProcNum),
|
|
builtin_translation(ModuleName, PredName, ProcNum, Args, CodePrime)
|
|
then
|
|
Code = CodePrime
|
|
else
|
|
list.length(Args, Arity),
|
|
string.format("unknown builtin %s/%d", [s(PredName), i(Arity)], Msg),
|
|
unexpected($pred, Msg)
|
|
).
|
|
|
|
:- pred builtin_translation(string::in, string::in, int::in, list(T)::in,
|
|
simple_code(T)::out(simple_code)) is semidet.
|
|
:- pragma inline(builtin_translation/5).
|
|
|
|
builtin_translation(ModuleName, PredName, ProcNum, Args, Code) :-
|
|
(
|
|
ModuleName = "builtin",
|
|
PredName = "unsafe_promise_unique", ProcNum = 0, Args = [X, Y],
|
|
Code = assign(Y, leaf(X))
|
|
;
|
|
ModuleName = "io",
|
|
(
|
|
PredName = "unsafe_get_io_state", ProcNum = 0, Args = [X],
|
|
Code = noop([X])
|
|
;
|
|
PredName = "unsafe_set_io_state", ProcNum = 0, Args = [_X],
|
|
Code = noop([])
|
|
)
|
|
;
|
|
ModuleName = "private_builtin",
|
|
(
|
|
PredName = "trace_get_io_state", ProcNum = 0, Args = [X],
|
|
Code = noop([X])
|
|
;
|
|
PredName = "trace_set_io_state", ProcNum = 0, Args = [_X],
|
|
Code = noop([])
|
|
;
|
|
PredName = "store_at_ref_impure",
|
|
ProcNum = 0, Args = [X, Y],
|
|
Code = ref_assign(X, Y)
|
|
;
|
|
PredName = "unsafe_type_cast", ProcNum = 0, Args = [X, Y],
|
|
% Note that the code we generate for unsafe_type_cast
|
|
% is not type-correct. Back-ends that require type-correct
|
|
% intermediate code (e.g. the MLDS back-end) must handle
|
|
% unsafe_type_cast separately, rather than by calling
|
|
% builtin_translation.
|
|
Code = assign(Y, leaf(X))
|
|
;
|
|
( PredName = "builtin_int_gt", CmpOp = int_gt(int_type_int)
|
|
; PredName = "builtin_int_lt", CmpOp = int_lt(int_type_int)
|
|
; PredName = "builtin_int8_gt", CmpOp = int_gt(int_type_int8)
|
|
; PredName = "builtin_int8_lt", CmpOp = int_lt(int_type_int8)
|
|
; PredName = "builtin_int16_gt", CmpOp = int_gt(int_type_int16)
|
|
; PredName = "builtin_int16_lt", CmpOp = int_lt(int_type_int16)
|
|
; PredName = "builtin_int32_gt", CmpOp = int_gt(int_type_int32)
|
|
; PredName = "builtin_int32_lt", CmpOp = int_lt(int_type_int32)
|
|
; PredName = "builtin_int64_gt", CmpOp = int_gt(int_type_int64)
|
|
; PredName = "builtin_int64_lt", CmpOp = int_lt(int_type_int64)
|
|
; PredName = "builtin_uint_gt", CmpOp = int_gt(int_type_uint)
|
|
; PredName = "builtin_uint_lt", CmpOp = int_lt(int_type_uint)
|
|
; PredName = "builtin_uint8_gt", CmpOp = int_gt(int_type_uint8)
|
|
; PredName = "builtin_uint8_lt", CmpOp = int_lt(int_type_uint8)
|
|
; PredName = "builtin_uint16_gt", CmpOp = int_gt(int_type_uint16)
|
|
; PredName = "builtin_uint16_lt", CmpOp = int_lt(int_type_uint16)
|
|
; PredName = "builtin_uint32_gt", CmpOp = int_gt(int_type_uint32)
|
|
; PredName = "builtin_uint32_lt", CmpOp = int_lt(int_type_uint32)
|
|
; PredName = "builtin_uint64_gt", CmpOp = int_gt(int_type_uint64)
|
|
; PredName = "builtin_uint64_lt", CmpOp = int_lt(int_type_uint64)
|
|
),
|
|
ProcNum = 0, Args = [X, Y],
|
|
Code = test(binary(CmpOp, leaf(X), leaf(Y)))
|
|
;
|
|
( PredName = "builtin_compound_eq", CmpOp = compound_eq
|
|
; PredName = "builtin_compound_lt", CmpOp = compound_lt
|
|
),
|
|
ProcNum = 0, Args = [X, Y],
|
|
Code = test(binary(CmpOp, leaf(X), leaf(Y)))
|
|
|
|
;
|
|
PredName = "pointer_equal", ProcNum = 0,
|
|
% The arity of this predicate is two during parsing,
|
|
% and three after the polymorphism pass.
|
|
( Args = [X, Y]
|
|
; Args = [_TypeInfo, X, Y]
|
|
),
|
|
Code = test(binary(pointer_equal_conservative, leaf(X), leaf(Y)))
|
|
)
|
|
;
|
|
ModuleName = "term_size_prof_builtin",
|
|
PredName = "term_size_plus", ProcNum = 0, Args = [X, Y, Z],
|
|
Code = assign(Z, binary(int_add(int_type_int), leaf(X), leaf(Y)))
|
|
;
|
|
( ModuleName = "int", IntType = int_type_int
|
|
; ModuleName = "uint", IntType = int_type_uint
|
|
; ModuleName = "int8", IntType = int_type_int8
|
|
; ModuleName = "uint8", IntType = int_type_uint8
|
|
; ModuleName = "int16", IntType = int_type_int16
|
|
; ModuleName = "uint16", IntType = int_type_uint16
|
|
; ModuleName = "int32", IntType = int_type_int32
|
|
; ModuleName = "uint32", IntType = int_type_uint32
|
|
; ModuleName = "int64", IntType = int_type_int64
|
|
; ModuleName = "uint64", IntType = int_type_uint64
|
|
),
|
|
(
|
|
PredName = "+",
|
|
(
|
|
Args = [X, Y, Z],
|
|
(
|
|
ProcNum = 0,
|
|
Code = assign(Z,
|
|
binary(int_add(IntType), leaf(X), leaf(Y)))
|
|
;
|
|
ProcNum = 1,
|
|
Code = assign(X,
|
|
binary(int_sub(IntType), leaf(Z), leaf(Y)))
|
|
;
|
|
ProcNum = 2,
|
|
Code = assign(Y,
|
|
binary(int_sub(IntType), leaf(Z), leaf(X)))
|
|
)
|
|
;
|
|
Args = [X, Y],
|
|
ProcNum = 0,
|
|
Code = assign(Y, leaf(X))
|
|
)
|
|
;
|
|
PredName = "-",
|
|
(
|
|
Args = [X, Y, Z],
|
|
(
|
|
ProcNum = 0,
|
|
Code = assign(Z,
|
|
binary(int_sub(IntType), leaf(X), leaf(Y)))
|
|
;
|
|
ProcNum = 1,
|
|
Code = assign(X,
|
|
binary(int_add(IntType), leaf(Y), leaf(Z)))
|
|
;
|
|
ProcNum = 2,
|
|
Code = assign(Y,
|
|
binary(int_sub(IntType), leaf(X), leaf(Z)))
|
|
)
|
|
;
|
|
Args = [X, Y],
|
|
ProcNum = 0,
|
|
IntZeroConst = make_int_zero_const(IntType),
|
|
Code = assign(Y,
|
|
binary(int_sub(IntType), IntZeroConst, leaf(X)))
|
|
)
|
|
;
|
|
PredName = "xor", Args = [X, Y, Z],
|
|
(
|
|
ProcNum = 0,
|
|
Code = assign(Z,
|
|
binary(bitwise_xor(IntType), leaf(X), leaf(Y)))
|
|
;
|
|
ProcNum = 1,
|
|
Code = assign(Y,
|
|
binary(bitwise_xor(IntType), leaf(X), leaf(Z)))
|
|
;
|
|
ProcNum = 2,
|
|
Code = assign(X,
|
|
binary(bitwise_xor(IntType), leaf(Y), leaf(Z)))
|
|
)
|
|
;
|
|
( PredName = "plus", ArithOp = int_add(IntType)
|
|
; PredName = "minus", ArithOp = int_sub(IntType)
|
|
; PredName = "*", ArithOp = int_mul(IntType)
|
|
; PredName = "times", ArithOp = int_mul(IntType)
|
|
; PredName = "unchecked_quotient", ArithOp = int_div(IntType)
|
|
; PredName = "unchecked_rem", ArithOp = int_mod(IntType)
|
|
; PredName = "unchecked_left_shift",
|
|
ArithOp = unchecked_left_shift(IntType)
|
|
; PredName = "unchecked_right_shift",
|
|
ArithOp = unchecked_right_shift(IntType)
|
|
; PredName = "/\\", ArithOp = bitwise_and(IntType)
|
|
; PredName = "\\/", ArithOp = bitwise_or(IntType)
|
|
),
|
|
ProcNum = 0, Args = [X, Y, Z],
|
|
Code = assign(Z, binary(ArithOp, leaf(X), leaf(Y)))
|
|
;
|
|
PredName = "\\", ProcNum = 0, Args = [X, Y],
|
|
Code = assign(Y, unary(bitwise_complement(IntType), leaf(X)))
|
|
;
|
|
( PredName = ">", CmpOp = int_gt(IntType)
|
|
; PredName = "<", CmpOp = int_lt(IntType)
|
|
; PredName = ">=", CmpOp = int_ge(IntType)
|
|
; PredName = "=<", CmpOp = int_le(IntType)
|
|
),
|
|
ProcNum = 0, Args = [X, Y],
|
|
Code = test(binary(CmpOp, leaf(X), leaf(Y)))
|
|
)
|
|
;
|
|
ModuleName = "float",
|
|
(
|
|
PredName = "+",
|
|
(
|
|
Args = [X, Y],
|
|
ProcNum = 0,
|
|
Code = assign(Y, leaf(X))
|
|
;
|
|
Args = [X, Y, Z],
|
|
ProcNum = 0,
|
|
Code = assign(Z, binary(float_plus, leaf(X), leaf(Y)))
|
|
)
|
|
;
|
|
PredName = "-",
|
|
(
|
|
Args = [X, Y],
|
|
ProcNum = 0,
|
|
Code = assign(Y,
|
|
binary(float_minus, float_const(0.0), leaf(X)))
|
|
;
|
|
Args = [X, Y, Z],
|
|
ProcNum = 0,
|
|
Code = assign(Z, binary(float_minus, leaf(X), leaf(Y)))
|
|
)
|
|
;
|
|
( PredName = "*", ArithOp = float_times
|
|
; PredName = "unchecked_quotient", ArithOp = float_divide
|
|
),
|
|
ProcNum = 0, Args = [X, Y, Z],
|
|
Code = assign(Z, binary(ArithOp, leaf(X), leaf(Y)))
|
|
;
|
|
( PredName = ">", CmpOp = float_gt
|
|
; PredName = "<", CmpOp = float_lt
|
|
; PredName = ">=", CmpOp = float_ge
|
|
; PredName = "=<", CmpOp = float_le
|
|
),
|
|
ProcNum = 0, Args = [X, Y],
|
|
Code = test(binary(CmpOp, leaf(X), leaf(Y)))
|
|
)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- func make_int_zero_const(int_type::in)
|
|
= (simple_expr(T)::out(simple_arg_expr)) is det.
|
|
|
|
make_int_zero_const(int_type_int) = int_const(0).
|
|
make_int_zero_const(int_type_int8) = int8_const(0i8).
|
|
make_int_zero_const(int_type_int16) = int16_const(0i16).
|
|
make_int_zero_const(int_type_int32) = int32_const(0i32).
|
|
make_int_zero_const(int_type_int64) = int64_const(0i64).
|
|
make_int_zero_const(int_type_uint) = uint_const(0u).
|
|
make_int_zero_const(int_type_uint8) = uint8_const(0u8).
|
|
make_int_zero_const(int_type_uint16) = uint16_const(0u16).
|
|
make_int_zero_const(int_type_uint32) = uint32_const(0u32).
|
|
make_int_zero_const(int_type_uint64) = uint64_const(0u64).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module backend_libs.builtin_ops.
|
|
%-----------------------------------------------------------------------------%
|