mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Experimental xtensa support
This commit is contained in:
@@ -112,6 +112,7 @@ enum ArchType
|
|||||||
WASM64, // WebAssembly with 64-bit pointers
|
WASM64, // WebAssembly with 64-bit pointers
|
||||||
RSCRIPT32, // 32-bit RenderScript
|
RSCRIPT32, // 32-bit RenderScript
|
||||||
RSCRIPT64, // 64-bit RenderScript
|
RSCRIPT64, // 64-bit RenderScript
|
||||||
|
XTENSA, // Xtensa
|
||||||
}
|
}
|
||||||
|
|
||||||
const OsType OS_TYPE = (OsType)$$OS_TYPE;
|
const OsType OS_TYPE = (OsType)$$OS_TYPE;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
- c3c init-lib does not create the directory with the .c3l suffix #1253
|
- c3c init-lib does not create the directory with the .c3l suffix #1253
|
||||||
- Permit foreach values to be optional.
|
- Permit foreach values to be optional.
|
||||||
- Add `--show-backtrace` option to disable backtrace for even smaller binary.
|
- Add `--show-backtrace` option to disable backtrace for even smaller binary.
|
||||||
|
- Untested Xtensa support.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
|||||||
@@ -317,6 +317,7 @@ typedef enum
|
|||||||
ELF_RISCV64,
|
ELF_RISCV64,
|
||||||
ELF_X86,
|
ELF_X86,
|
||||||
ELF_X64,
|
ELF_X64,
|
||||||
|
ELF_XTENSA,
|
||||||
FREEBSD_X86,
|
FREEBSD_X86,
|
||||||
FREEBSD_X64,
|
FREEBSD_X64,
|
||||||
LINUX_AARCH64,
|
LINUX_AARCH64,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ char *arch_os_target[ARCH_OS_TARGET_LAST + 1] = {
|
|||||||
[ELF_RISCV64] = "elf-riscv64",
|
[ELF_RISCV64] = "elf-riscv64",
|
||||||
[ELF_X86] = "elf-x86",
|
[ELF_X86] = "elf-x86",
|
||||||
[ELF_X64] = "elf-x64",
|
[ELF_X64] = "elf-x64",
|
||||||
|
[ELF_XTENSA] = "elf-xtensa",
|
||||||
[FREEBSD_X86] = "freebsd-x86",
|
[FREEBSD_X86] = "freebsd-x86",
|
||||||
[FREEBSD_X64] = "freebsd-x64",
|
[FREEBSD_X64] = "freebsd-x64",
|
||||||
[LINUX_AARCH64] = "linux-aarch64",
|
[LINUX_AARCH64] = "linux-aarch64",
|
||||||
|
|||||||
@@ -197,25 +197,32 @@ void c_abi_func_create(FunctionPrototype *proto)
|
|||||||
{
|
{
|
||||||
case ABI_X64:
|
case ABI_X64:
|
||||||
c_abi_func_create_x64(proto);
|
c_abi_func_create_x64(proto);
|
||||||
break;
|
return;
|
||||||
case ABI_X86:
|
case ABI_X86:
|
||||||
c_abi_func_create_x86(proto);
|
c_abi_func_create_x86(proto);
|
||||||
break;
|
return;
|
||||||
case ABI_WIN64:
|
case ABI_WIN64:
|
||||||
c_abi_func_create_win64(proto);
|
c_abi_func_create_win64(proto);
|
||||||
break;
|
return;
|
||||||
case ABI_AARCH64:
|
case ABI_AARCH64:
|
||||||
c_abi_func_create_aarch64(proto);
|
c_abi_func_create_aarch64(proto);
|
||||||
break;
|
return;
|
||||||
case ABI_RISCV:
|
case ABI_RISCV:
|
||||||
c_abi_func_create_riscv(proto);
|
c_abi_func_create_riscv(proto);
|
||||||
break;
|
return;
|
||||||
case ABI_WASM:
|
case ABI_WASM:
|
||||||
c_abi_func_create_wasm(proto);
|
c_abi_func_create_wasm(proto);
|
||||||
|
return;
|
||||||
|
case ABI_XTENSA:
|
||||||
|
c_abi_func_create_default(proto);
|
||||||
|
return;
|
||||||
|
case ABI_UNKNOWN:
|
||||||
|
case ABI_ARM:
|
||||||
|
case ABI_PPC32:
|
||||||
|
case ABI_PPC64_SVR4:
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
FATAL_ERROR("Unsupported ABI");
|
|
||||||
}
|
}
|
||||||
|
FATAL_ERROR("Unsupported ABI");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -242,3 +249,34 @@ ABIArgInfo *c_abi_classify_argument_type_default(Type *type)
|
|||||||
return abi_arg_new_direct();
|
return abi_arg_new_direct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void c_abi_func_create_default(FunctionPrototype *prototype)
|
||||||
|
{
|
||||||
|
prototype->ret_abi_info = c_abi_classify_return_type_default(prototype->abi_ret_type);
|
||||||
|
if (prototype->ret_by_ref)
|
||||||
|
{
|
||||||
|
prototype->ret_by_ref_abi_info = c_abi_classify_return_type_default(type_get_ptr(type_flatten(prototype->ret_by_ref_type)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Type **params = prototype->param_types;
|
||||||
|
unsigned param_count = vec_size(prototype->param_types);
|
||||||
|
if (param_count)
|
||||||
|
{
|
||||||
|
ABIArgInfo **args = MALLOC(sizeof(ABIArgInfo) * param_count);
|
||||||
|
for (unsigned i = 0; i < param_count; i++)
|
||||||
|
{
|
||||||
|
args[i] = c_abi_classify_argument_type_default(params[i]);
|
||||||
|
}
|
||||||
|
prototype->abi_args = args;
|
||||||
|
}
|
||||||
|
Type **va_params = prototype->varargs;
|
||||||
|
unsigned va_param_count = vec_size(va_params);
|
||||||
|
if (va_param_count)
|
||||||
|
{
|
||||||
|
ABIArgInfo **args = MALLOC(sizeof(ABIArgInfo) * va_param_count);
|
||||||
|
for (unsigned i = 0; i < va_param_count; i++)
|
||||||
|
{
|
||||||
|
args[i] = c_abi_classify_argument_type_default(va_params[i]);
|
||||||
|
}
|
||||||
|
prototype->abi_varargs = args;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -459,6 +459,8 @@ void init_asm(void)
|
|||||||
case ARCH_TYPE_WASM32:
|
case ARCH_TYPE_WASM32:
|
||||||
init_asm_wasm();
|
init_asm_wasm();
|
||||||
return;
|
return;
|
||||||
|
case ARCH_TYPE_XTENSA:
|
||||||
|
error_exit("Xtensa asm support not yet available.");
|
||||||
case ARCH_TYPE_UNKNOWN:
|
case ARCH_TYPE_UNKNOWN:
|
||||||
error_exit("Unknown arch does not support asm.");
|
error_exit("Unknown arch does not support asm.");
|
||||||
UNREACHABLE
|
UNREACHABLE
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ void c_abi_func_create_x64(FunctionPrototype *prototype);
|
|||||||
void c_abi_func_create_aarch64(FunctionPrototype *prototype);
|
void c_abi_func_create_aarch64(FunctionPrototype *prototype);
|
||||||
void c_abi_func_create_riscv(FunctionPrototype *prototype);
|
void c_abi_func_create_riscv(FunctionPrototype *prototype);
|
||||||
void c_abi_func_create_wasm(FunctionPrototype *prototype);
|
void c_abi_func_create_wasm(FunctionPrototype *prototype);
|
||||||
|
void c_abi_func_create_default(FunctionPrototype *prototype);
|
||||||
|
|
||||||
static inline AbiType abi_type_get(Type *type)
|
static inline AbiType abi_type_get(Type *type)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ bool type_is_homogenous_base_type(Type *type)
|
|||||||
case ABI_WASM:
|
case ABI_WASM:
|
||||||
case ABI_PPC32:
|
case ABI_PPC32:
|
||||||
case ABI_RISCV:
|
case ABI_RISCV:
|
||||||
|
case ABI_XTENSA:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
UNREACHABLE
|
UNREACHABLE
|
||||||
@@ -150,6 +151,7 @@ bool type_homogenous_aggregate_small_enough(Type *type, unsigned members)
|
|||||||
case ABI_WASM:
|
case ABI_WASM:
|
||||||
case ABI_PPC32:
|
case ABI_PPC32:
|
||||||
case ABI_RISCV:
|
case ABI_RISCV:
|
||||||
|
case ABI_XTENSA:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
UNREACHABLE
|
UNREACHABLE
|
||||||
|
|||||||
@@ -990,6 +990,9 @@ static int jump_buffer_size()
|
|||||||
case LINUX_RISCV32:
|
case LINUX_RISCV32:
|
||||||
// Godbolt test
|
// Godbolt test
|
||||||
return 76;
|
return 76;
|
||||||
|
case ELF_XTENSA:
|
||||||
|
// Godbolt 68 => 17 with 32 bit pointers
|
||||||
|
return 17;
|
||||||
case ELF_RISCV64:
|
case ELF_RISCV64:
|
||||||
case LINUX_RISCV64:
|
case LINUX_RISCV64:
|
||||||
// Godbolt test
|
// Godbolt test
|
||||||
|
|||||||
@@ -1028,6 +1028,8 @@ const char *arch_to_linker_arch(ArchType arch)
|
|||||||
return "wasm32";
|
return "wasm32";
|
||||||
case ARCH_TYPE_WASM64:
|
case ARCH_TYPE_WASM64:
|
||||||
return "wasm64";
|
return "wasm64";
|
||||||
|
case ARCH_TYPE_XTENSA:
|
||||||
|
return "xtensa";
|
||||||
}
|
}
|
||||||
UNREACHABLE;
|
UNREACHABLE;
|
||||||
}
|
}
|
||||||
@@ -1055,6 +1057,7 @@ static char *arch_to_target_triple[ARCH_OS_TARGET_LAST + 1] = {
|
|||||||
[ELF_RISCV32] = "riscv32-unknown-elf",
|
[ELF_RISCV32] = "riscv32-unknown-elf",
|
||||||
[LINUX_RISCV64] = "riscv64-unknown-linux",
|
[LINUX_RISCV64] = "riscv64-unknown-linux",
|
||||||
[ELF_RISCV64] = "riscv64-unknown-elf",
|
[ELF_RISCV64] = "riscv64-unknown-elf",
|
||||||
|
[ELF_XTENSA] = "xtensa-unknown-elf",
|
||||||
[WASM32] = "wasm32-unknown-unknown",
|
[WASM32] = "wasm32-unknown-unknown",
|
||||||
[WASM64] = "wasm64-unknown-unknown",
|
[WASM64] = "wasm64-unknown-unknown",
|
||||||
};
|
};
|
||||||
@@ -1144,6 +1147,7 @@ static ArchType arch_from_llvm_string(StringSlice slice)
|
|||||||
STRCASE("amd64", ARCH_TYPE_X86_64)
|
STRCASE("amd64", ARCH_TYPE_X86_64)
|
||||||
STRCASE("x86_64h", ARCH_TYPE_X86_64)
|
STRCASE("x86_64h", ARCH_TYPE_X86_64)
|
||||||
STRCASE("xcore", ARCH_TYPE_XCORE)
|
STRCASE("xcore", ARCH_TYPE_XCORE)
|
||||||
|
STRCASE("xtensa", ARCH_TYPE_XTENSA)
|
||||||
STRCASE("nvptx", ARCH_TYPE_NVPTX)
|
STRCASE("nvptx", ARCH_TYPE_NVPTX)
|
||||||
STRCASE("nvptx64", ARCH_TYPE_NVPTX64)
|
STRCASE("nvptx64", ARCH_TYPE_NVPTX64)
|
||||||
STRCASE("le32", ARCH_TYPE_LE32)
|
STRCASE("le32", ARCH_TYPE_LE32)
|
||||||
@@ -1297,6 +1301,7 @@ static unsigned arch_pointer_bit_width(OsType os, ArchType arch)
|
|||||||
case ARCH_TYPE_THUMBEB:
|
case ARCH_TYPE_THUMBEB:
|
||||||
case ARCH_TYPE_X86:
|
case ARCH_TYPE_X86:
|
||||||
case ARCH_TYPE_WASM32:
|
case ARCH_TYPE_WASM32:
|
||||||
|
case ARCH_TYPE_XTENSA:
|
||||||
return 32;
|
return 32;
|
||||||
case ARCH_TYPE_WASM64:
|
case ARCH_TYPE_WASM64:
|
||||||
case ARCH_TYPE_AARCH64:
|
case ARCH_TYPE_AARCH64:
|
||||||
@@ -1310,9 +1315,8 @@ static unsigned arch_pointer_bit_width(OsType os, ArchType arch)
|
|||||||
case ARCH_TYPE_X86_64:
|
case ARCH_TYPE_X86_64:
|
||||||
if (os == OS_TYPE_NACL) return 32;
|
if (os == OS_TYPE_NACL) return 32;
|
||||||
return 64;
|
return 64;
|
||||||
default:
|
|
||||||
UNREACHABLE
|
|
||||||
}
|
}
|
||||||
|
UNREACHABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned arch_int_register_bit_width(OsType os, ArchType arch)
|
static unsigned arch_int_register_bit_width(OsType os, ArchType arch)
|
||||||
@@ -1330,6 +1334,7 @@ static unsigned arch_int_register_bit_width(OsType os, ArchType arch)
|
|||||||
case ARCH_TYPE_THUMBEB:
|
case ARCH_TYPE_THUMBEB:
|
||||||
case ARCH_TYPE_X86:
|
case ARCH_TYPE_X86:
|
||||||
case ARCH_TYPE_WASM32:
|
case ARCH_TYPE_WASM32:
|
||||||
|
case ARCH_TYPE_XTENSA:
|
||||||
return 32;
|
return 32;
|
||||||
case ARCH_TYPE_WASM64:
|
case ARCH_TYPE_WASM64:
|
||||||
case ARCH_TYPE_AARCH64:
|
case ARCH_TYPE_AARCH64:
|
||||||
@@ -1343,9 +1348,8 @@ static unsigned arch_int_register_bit_width(OsType os, ArchType arch)
|
|||||||
case ARCH_TYPE_X86_64:
|
case ARCH_TYPE_X86_64:
|
||||||
if (os == OS_TYPE_NACL) return 32;
|
if (os == OS_TYPE_NACL) return 32;
|
||||||
return 64;
|
return 64;
|
||||||
default:
|
|
||||||
UNREACHABLE
|
|
||||||
}
|
}
|
||||||
|
UNREACHABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned os_target_supports_float16(OsType os, ArchType arch)
|
static unsigned os_target_supports_float16(OsType os, ArchType arch)
|
||||||
@@ -1443,6 +1447,7 @@ static unsigned os_target_supports_int128(OsType os, ArchType arch)
|
|||||||
case ARCH_TYPE_WASM64:
|
case ARCH_TYPE_WASM64:
|
||||||
return true;
|
return true;
|
||||||
case ARCH_TYPE_PPC:
|
case ARCH_TYPE_PPC:
|
||||||
|
case ARCH_TYPE_XTENSA:
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1547,6 +1552,7 @@ static AlignData os_target_alignment_of_int(OsType os, ArchType arch, uint32_t b
|
|||||||
case ARCH_TYPE_WASM64:
|
case ARCH_TYPE_WASM64:
|
||||||
case ARCH_TYPE_RISCV32:
|
case ARCH_TYPE_RISCV32:
|
||||||
case ARCH_TYPE_WASM32:
|
case ARCH_TYPE_WASM32:
|
||||||
|
case ARCH_TYPE_XTENSA:
|
||||||
return (AlignData) { MIN(64u, bits), MIN(64u, bits) };
|
return (AlignData) { MIN(64u, bits), MIN(64u, bits) };
|
||||||
case ARCH_TYPE_RISCV64:
|
case ARCH_TYPE_RISCV64:
|
||||||
return (AlignData) { bits, bits };
|
return (AlignData) { bits, bits };
|
||||||
@@ -1578,6 +1584,7 @@ static unsigned arch_big_endian(ArchType arch)
|
|||||||
case ARCH_TYPE_RISCV64:
|
case ARCH_TYPE_RISCV64:
|
||||||
case ARCH_TYPE_WASM32:
|
case ARCH_TYPE_WASM32:
|
||||||
case ARCH_TYPE_WASM64:
|
case ARCH_TYPE_WASM64:
|
||||||
|
case ARCH_TYPE_XTENSA:
|
||||||
return false;
|
return false;
|
||||||
case ARCH_TYPE_ARMB:
|
case ARCH_TYPE_ARMB:
|
||||||
case ARCH_TYPE_THUMBEB:
|
case ARCH_TYPE_THUMBEB:
|
||||||
@@ -1615,6 +1622,7 @@ static AlignData os_target_alignment_of_float(OsType os, ArchType arch, uint32_t
|
|||||||
case ARCH_TYPE_RISCV64:
|
case ARCH_TYPE_RISCV64:
|
||||||
case ARCH_TYPE_WASM32:
|
case ARCH_TYPE_WASM32:
|
||||||
case ARCH_TYPE_WASM64:
|
case ARCH_TYPE_WASM64:
|
||||||
|
case ARCH_TYPE_XTENSA:
|
||||||
return (AlignData) { bits , bits };
|
return (AlignData) { bits , bits };
|
||||||
case ARCH_TYPE_ARM:
|
case ARCH_TYPE_ARM:
|
||||||
case ARCH_TYPE_THUMB:
|
case ARCH_TYPE_THUMB:
|
||||||
@@ -1752,6 +1760,13 @@ INLINE const char *llvm_macos_target_triple(const char *triple)
|
|||||||
scratch_buffer_printf("%d.%d.0", mac_sdk->macos_min_deploy_target.major, mac_sdk->macos_min_deploy_target.minor);
|
scratch_buffer_printf("%d.%d.0", mac_sdk->macos_min_deploy_target.major, mac_sdk->macos_min_deploy_target.minor);
|
||||||
return scratch_buffer_to_string();
|
return scratch_buffer_to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if LLVM_VERSION_MAJOR > 19
|
||||||
|
#define XTENSA_AVAILABLE 1
|
||||||
|
#else
|
||||||
|
#define XTENSA_AVAILABLE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
void *llvm_target_machine_create(void)
|
void *llvm_target_machine_create(void)
|
||||||
{
|
{
|
||||||
static bool llvm_initialized = false;
|
static bool llvm_initialized = false;
|
||||||
@@ -1759,6 +1774,9 @@ void *llvm_target_machine_create(void)
|
|||||||
if (!llvm_initialized)
|
if (!llvm_initialized)
|
||||||
{
|
{
|
||||||
llvm_initialized = true;
|
llvm_initialized = true;
|
||||||
|
#if XTENSA_AVAILABLE
|
||||||
|
INITIALIZE_TARGET(Xtensa);
|
||||||
|
#endif
|
||||||
INITIALIZE_TARGET(ARM);
|
INITIALIZE_TARGET(ARM);
|
||||||
INITIALIZE_TARGET(AArch64);
|
INITIALIZE_TARGET(AArch64);
|
||||||
INITIALIZE_TARGET(RISCV);
|
INITIALIZE_TARGET(RISCV);
|
||||||
@@ -1821,7 +1839,13 @@ void target_setup(BuildTarget *target)
|
|||||||
error_exit("Unable to detect the default target, please set an explicit --target value.");
|
error_exit("Unable to detect the default target, please set an explicit --target value.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (target->arch_os_target == ELF_XTENSA && !XTENSA_AVAILABLE)
|
||||||
|
{
|
||||||
|
error_exit("Xtensa support is not available with this LLVM version.");
|
||||||
|
}
|
||||||
|
|
||||||
platform_target.target_triple = arch_to_target_triple[target->arch_os_target];
|
platform_target.target_triple = arch_to_target_triple[target->arch_os_target];
|
||||||
|
assert(platform_target.target_triple);
|
||||||
|
|
||||||
platform_target.alloca_address_space = 0;
|
platform_target.alloca_address_space = 0;
|
||||||
|
|
||||||
@@ -1856,7 +1880,7 @@ void target_setup(BuildTarget *target)
|
|||||||
platform_target.arch = arch_from_llvm_string(slice_next_token(&target_triple_string, '-'));
|
platform_target.arch = arch_from_llvm_string(slice_next_token(&target_triple_string, '-'));
|
||||||
if (!arch_is_supported(platform_target.arch))
|
if (!arch_is_supported(platform_target.arch))
|
||||||
{
|
{
|
||||||
printf("WARNING! This architecture is not supported.\n");
|
printf("WARNING! This architecture is unsupported.\n");
|
||||||
}
|
}
|
||||||
platform_target.vendor = vendor_from_llvm_string(slice_next_token(&target_triple_string, '-'));
|
platform_target.vendor = vendor_from_llvm_string(slice_next_token(&target_triple_string, '-'));
|
||||||
platform_target.os = os_from_llvm_string(slice_next_token(&target_triple_string, '-'));
|
platform_target.os = os_from_llvm_string(slice_next_token(&target_triple_string, '-'));
|
||||||
@@ -1924,6 +1948,9 @@ void target_setup(BuildTarget *target)
|
|||||||
platform_target.aarch.is_win32 = platform_target.os == OS_TYPE_WIN32;
|
platform_target.aarch.is_win32 = platform_target.os == OS_TYPE_WIN32;
|
||||||
platform_target.abi = ABI_AARCH64;
|
platform_target.abi = ABI_AARCH64;
|
||||||
break;
|
break;
|
||||||
|
case ARCH_TYPE_XTENSA:
|
||||||
|
platform_target.abi = ABI_XTENSA;
|
||||||
|
break;
|
||||||
case ARCH_TYPE_WASM32:
|
case ARCH_TYPE_WASM32:
|
||||||
case ARCH_TYPE_WASM64:
|
case ARCH_TYPE_WASM64:
|
||||||
platform_target.abi = ABI_WASM;
|
platform_target.abi = ABI_WASM;
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ typedef enum
|
|||||||
ARCH_TYPE_WASM64, // WebAssembly with 64-bit pointers
|
ARCH_TYPE_WASM64, // WebAssembly with 64-bit pointers
|
||||||
ARCH_TYPE_RSCRIPT32, // 32-bit RenderScript
|
ARCH_TYPE_RSCRIPT32, // 32-bit RenderScript
|
||||||
ARCH_TYPE_RSCRIPT64, // 64-bit RenderScript
|
ARCH_TYPE_RSCRIPT64, // 64-bit RenderScript
|
||||||
ARCH_TYPE_LAST = ARCH_TYPE_RSCRIPT64
|
ARCH_TYPE_XTENSA, // Xtensa
|
||||||
|
ARCH_TYPE_LAST = ARCH_TYPE_XTENSA
|
||||||
|
|
||||||
} ArchType;
|
} ArchType;
|
||||||
|
|
||||||
@@ -201,6 +202,7 @@ typedef enum
|
|||||||
ABI_PPC32,
|
ABI_PPC32,
|
||||||
ABI_PPC64_SVR4,
|
ABI_PPC64_SVR4,
|
||||||
ABI_RISCV,
|
ABI_RISCV,
|
||||||
|
ABI_XTENSA,
|
||||||
} ABI;
|
} ABI;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user