Add "--enable-builtin-intrinsics=<flags>" option to wamrc (#2341)

Refer to doc/xip.md for details.
This commit is contained in:
Huang Qi 2023-07-06 18:20:35 +08:00 committed by GitHub
parent 228a3bed53
commit aafea39b8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 259 additions and 0 deletions

View File

@ -648,6 +648,42 @@ add_f64_common_intrinsics(AOTCompContext *comp_ctx)
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_F64_CMP);
}
static void
add_f32xi32_intrinsics(AOTCompContext *comp_ctx)
{
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_F32_TO_I32);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_F32_TO_U32);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_I32_TO_F32);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_U32_TO_F32);
}
static void
add_f64xi32_intrinsics(AOTCompContext *comp_ctx)
{
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_F64_TO_I32);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_F64_TO_U32);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_I32_TO_F64);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_U32_TO_F64);
}
static void
add_f32xi64_intrinsics(AOTCompContext *comp_ctx)
{
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_F32_TO_I64);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_F32_TO_U64);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_I64_TO_F32);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_U64_TO_F32);
}
static void
add_f64xi64_intrinsics(AOTCompContext *comp_ctx)
{
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_F64_TO_I64);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_F64_TO_U64);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_I64_TO_F64);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_U64_TO_F64);
}
static void
add_common_float_integer_convertion(AOTCompContext *comp_ctx)
{
@ -705,8 +741,101 @@ aot_intrinsic_check_capability(const AOTCompContext *comp_ctx,
void
aot_intrinsic_fill_capability_flags(AOTCompContext *comp_ctx)
{
uint32 i;
memset(comp_ctx->flags, 0, sizeof(comp_ctx->flags));
/* Intrinsics from command line have highest priority */
if (comp_ctx->builtin_intrinsics) {
/* Handle 'all' group */
if (strstr(comp_ctx->builtin_intrinsics, "all")) {
for (i = 0; i < g_intrinsic_count; i++) {
add_intrinsic_capability(comp_ctx, g_intrinsic_mapping[i].flag);
}
return;
}
/* Handle 'i32.common' group */
if (strstr(comp_ctx->builtin_intrinsics, "i32.common")) {
add_i32_common_intrinsics(comp_ctx);
}
/* Handle 'i64.common' group */
if (strstr(comp_ctx->builtin_intrinsics, "i64.common")) {
add_i64_common_intrinsics(comp_ctx);
}
/* Handle 'fp.common' group */
if (strstr(comp_ctx->builtin_intrinsics, "fp.common")) {
add_f32_common_intrinsics(comp_ctx);
add_f64_common_intrinsics(comp_ctx);
}
/* Handle 'f32.common' group */
if (strstr(comp_ctx->builtin_intrinsics, "f32.common")) {
add_f32_common_intrinsics(comp_ctx);
}
/* Handle 'f64.common' group */
if (strstr(comp_ctx->builtin_intrinsics, "f64.common")) {
add_f64_common_intrinsics(comp_ctx);
}
/* Handle 'f32xi32' group */
if (strstr(comp_ctx->builtin_intrinsics, "f32xi32")) {
add_f32xi32_intrinsics(comp_ctx);
}
/* Handle 'f64xi32' group */
if (strstr(comp_ctx->builtin_intrinsics, "f64xi32")) {
add_f64xi32_intrinsics(comp_ctx);
}
/* Handle 'f32xi64' group */
if (strstr(comp_ctx->builtin_intrinsics, "f32xi64")) {
add_f32xi64_intrinsics(comp_ctx);
}
/* Handle 'f64xi64' group */
if (strstr(comp_ctx->builtin_intrinsics, "f64xi64")) {
add_f64xi64_intrinsics(comp_ctx);
}
/* Handle 'fpxint' group */
if (strstr(comp_ctx->builtin_intrinsics, "fpxint")) {
add_f32xi32_intrinsics(comp_ctx);
add_f64xi32_intrinsics(comp_ctx);
add_f32xi64_intrinsics(comp_ctx);
add_f64xi64_intrinsics(comp_ctx);
}
/* Handle 'constop' group */
if (strstr(comp_ctx->builtin_intrinsics, "constop")) {
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_I32_CONST);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_I64_CONST);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_F32_CONST);
add_intrinsic_capability(comp_ctx, AOT_INTRINSIC_FLAG_F64_CONST);
}
/* Handle 'fp.common' group */
if (strstr(comp_ctx->builtin_intrinsics, "fp.common")) {
add_f32_common_intrinsics(comp_ctx);
add_f64_common_intrinsics(comp_ctx);
}
/* Handle other single items */
for (i = 0; i < g_intrinsic_count; i++) {
if (strstr(comp_ctx->builtin_intrinsics,
g_intrinsic_mapping[i].llvm_intrinsic)) {
add_intrinsic_capability(comp_ctx, g_intrinsic_mapping[i].flag);
}
}
return;
}
if (!comp_ctx->target_cpu)
return;

View File

@ -2308,6 +2308,9 @@ aot_create_comp_context(const AOTCompData *comp_data, aot_comp_option_t option)
if (option->llvm_passes)
comp_ctx->llvm_passes = option->llvm_passes;
if (option->builtin_intrinsics)
comp_ctx->builtin_intrinsics = option->builtin_intrinsics;
comp_ctx->opt_level = option->opt_level;
comp_ctx->size_level = option->size_level;

View File

@ -418,6 +418,7 @@ typedef struct AOTCompContext {
const char *stack_usage_file;
char stack_usage_temp_file[64];
const char *llvm_passes;
const char *builtin_intrinsics;
} AOTCompContext;
enum {
@ -457,6 +458,7 @@ typedef struct AOTCompOption {
uint32 custom_sections_count;
const char *stack_usage_file;
const char *llvm_passes;
const char *builtin_intrinsics;
} AOTCompOption, *aot_comp_option_t;
bool

View File

@ -68,6 +68,7 @@ typedef struct AOTCompOption {
uint32_t custom_sections_count;
const char *stack_usage_file;
const char *llvm_passes;
const char *builtin_intrinsics;
} AOTCompOption, *aot_comp_option_t;
bool

View File

@ -16,3 +16,116 @@ Note: --xip is a short option for --enable-indirect-mode --disable-llvm-intrinsi
## Known issues
There may be some relocations to the ".rodata" like sections which require to patch the AOT code. More work will be done to resolve it in the future.
## Tuning the XIP intrinsic functions
WAMR provides a default mapping table for some targets, but it may not be the best one for your target. And it doesn't cover all the supported targets.
So, wamrc provides the option `--enable-builtin-intrinsics=<intr1,intr2,...>` to make it possible to tune the intrinsic functions for your target.
Firstly, you should understand why we don't use the LLVM intrinsic functions directly. The reason is that the LLVM intrinsic functions can't map to the native instructions directly, e.g. the LLVM intrinsic function `i32.div_s` can't map to the native instruction if the target doesn't support the division instruction, it will be translated to a function call to the runtime function from libgcc/compiler-rt. This will cause the AOT code to have the relocations to the libgcc/compiler-rt, which is not acceptable for the XIP feature.
So, we need to replace the LLVM intrinsic functions with the runtime self implemented functions, which can be called through the function pointer table (--enable-indirect-mode) and don't have the relocations to the libgcc/compiler-rt (--disable-llvm-intrinsics).
Available intrinsic functions for tuning:
| LLVM intrinsic function | Explanation |
| --- | --- |
| llvm.experimental.constrained.fadd.f32 | float32 add |
| llvm.experimental.constrained.fadd.f64 | float64 add |
| llvm.experimental.constrained.fsub.f32 | float32 sub |
| llvm.experimental.constrained.fsub.f64 | float64 sub |
| llvm.experimental.constrained.fmul.f32 | float32 mul |
| llvm.experimental.constrained.fmul.f64 | float64 mul |
| llvm.experimental.constrained.fdiv.f32 | float32 div |
| llvm.experimental.constrained.fdiv.f64 | float64 div |
| llvm.fabs.f32 | float32 abs |
| llvm.fabs.f64 | float64 abs |
| llvm.ceil.f32 | float32 ceil |
| llvm.ceil.f64 | float64 ceil |
| llvm.floor.f32 | float32 floor |
| llvm.floor.f64 | float64 floor |
| llvm.trunc.f32 | float32 trunc |
| llvm.trunc.f64 | float64 trunc |
| llvm.rint.f32 | float32 rint |
| llvm.rint.f64 | float64 rint |
| llvm.sqrt.f32 | float32 sqrt |
| llvm.sqrt.f64 | float64 sqrt |
| llvm.copysign.f32 | float32 copysign |
| llvm.copysign.f64 | float64 copysign |
| llvm.minnum.f32 | float32 minnum |
| llvm.minnum.f64 | float64 minnum |
| llvm.maxnum.f32 | float32 maxnum |
| llvm.maxnum.f64 | float64 maxnum |
| llvm.ctlz.i32 | int32 count leading zeros |
| llvm.ctlz.i64 | int64 count leading zeros |
| llvm.cttz.i32 | int32 count trailing zeros |
| llvm.cttz.i64 | int64 count trailing zeros |
| llvm.ctpop.i32 | int32 count population |
| llvm.ctpop.i64 | int64 count population |
| f64_convert_i32_s | int32 to float64 |
| f64_convert_i32_u | uint32 to float64 |
| f32_convert_i32_s | int32 to float32 |
| f32_convert_i32_u | uint32 to float32 |
| f64_convert_i64_s | int64 to float64 |
| f64_convert_i64_u | uint64 to float64 |
| f32_convert_i64_s | int64 to float32 |
| f32_convert_i64_u | uint64 to float32 |
| i32_trunc_f32_s | float32 to int32 |
| i32_trunc_f32_u | float32 to uint32 |
| i32_trunc_f64_s | float64 to int32 |
| i32_trunc_f64_u | float64 to uint32 |
| i64_trunc_f64_s | float64 to int64 |
| i64_trunc_f64_u | float64 to uint64 |
| i64_trunc_f32_s | float32 to int64 |
| i64_trunc_f32_u | float32 to uint64 |
| f32_demote_f64 | float64 to float32 |
| f64_promote_f32 | float32 to float64 |
| f32_cmp | float32 compare |
| f64_cmp | float64 compare |
| i64.div_s | int64 div |
| i64.div_u | uint64 div |
| i32.div_s | int32 div |
| i32.div_u | uint32 div |
| i64.rem_s | int64 rem |
| i64.rem_u | uint64 rem |
| i32.rem_s | int32 rem |
| i32.rem_u | uint32 rem |
| i64.or | int64 or |
| i64.and | int64 and |
| i32.const | emit i32 const into constant table |
| i64.const | emit i64 const into constant table |
| f32.const | emit f32 const into constant table |
| f64.const | emit f64 const into constant table |
And also provide combined intrinsic functions to simplify the tuning:
* all: all the above intrinsic functions
* i32.common: i32.div_s, i32.div_u, i32.rem_s, i32.rem_u
* i64.common: i64.div_s, i64.div_u, i64.rem_s, i64.rem_u, i64.or, i64.and
* f32.common: f32_cmp, llvm.experimental.constrained.fadd.f32, llvm.experimental.constrained.fsub.f32, llvm.experimental.constrained.fmul.f32, llvm.experimental.constrained.fdiv.f32, llvm.fabs.f32, llvm.ceil.f32, llvm.floor.f32, llvm.trunc.f32, llvm.rint.f32, llvm.sqrt.f32, llvm.copysign.f32, llvm.minnum.f32, llvm.maxnum.f32
* f64.common: f32_demote_f64, f64_promote_f32, f64_cmp, llvm.experimental.constrained.fadd.f64, llvm.experimental.constrained.fsub.f64, llvm.experimental.constrained.fmul.f64, llvm.experimental.constrained.fdiv.f64, llvm.fabs.f64, llvm.ceil.f64, llvm.floor.f64, llvm.trunc.f64, llvm.rint.f64, llvm.sqrt.f64, llvm.copysign.f64, llvm.minnum.f64, llvm.maxnum.f64
* f32xi32: i32_trunc_f32_s, i32_trunc_f32_u, f32_convert_i32_s, f32_convert_i32_u
* f64xi32: i32_trunc_f64_s, i32_trunc_f64_u, f64_convert_i32_s, f64_convert_i32_u
* f32xi64: i64_trunc_f32_s, i64_trunc_f32_u, f32_convert_i64_s, f32_convert_i64_u
* f64xi64: i64_trunc_f64_s, i64_trunc_f64_u, f64_convert_i64_s, f64_convert_i64_u
* constop: i32.const, i64.const, f32.const, f64.const
* fpxint: f32xi32, f64xi32, f32xi64, f64xi64
* fp.common: f32.common, f64.common
### Example
For ARM Cortex-M55, since it has double precision floating point unit, so it can support f32/f64 operations. But as a 32-bit MCU, it can only support 32-bit integer operations. So we can use the following command to generate the XIP binary:
```
wamrc --target=thumbv8m.main --cpu=cortex-m55 --xip --enable-builtin-intrinsics=i64.common -o hello.aot hello.wasm
```
For ARM Cortex-M3, since it has no floating point unit, and it can only support 32-bit integer operations. So we can use the following command to generate the XIP binary:
```
wamrc --target=thumbv7m --cpu=cortex-m3 --xip --enable-builtin-intrinsics=i64.common,fp.common,fpxint -o hello.aot hello.wasm
```
Other platforms can be tuned in the same way, which intrinsic should be enabled depends on the target platform's hardware capability.

View File

@ -162,6 +162,12 @@ print_help()
printf(" --xip A shorthand of --enalbe-indirect-mode --disable-llvm-intrinsics\n");
printf(" --enable-indirect-mode Enalbe call function through symbol table but not direct call\n");
printf(" --disable-llvm-intrinsics Disable the LLVM built-in intrinsics\n");
printf(" --enable-builtin-intrinsics=<flags>\n");
printf(" Enable the specified built-in intrinsics, it will override the default\n");
printf(" settings. It only takes effect when --disable-llvm-intrinsics is set.\n");
printf(" Available flags: all, i32.common, i64.common, f32.common, f64.common,\n");
printf(" i32.clz, i32.ctz, etc, refer to doc/xip.md for full list\n");
printf(" Use comma to separate, please refer to doc/xip.md for full list.\n");
printf(" --disable-llvm-lto Disable the LLVM link time optimization\n");
printf(" --enable-llvm-pgo Enable LLVM PGO (Profile-Guided Optimization)\n");
printf(" --enable-llvm-passes=<passes>\n");
@ -443,6 +449,11 @@ main(int argc, char *argv[])
else if (!strcmp(argv[0], "--disable-llvm-intrinsics")) {
option.disable_llvm_intrinsics = true;
}
else if (!strncmp(argv[0], "--enable-builtin-intrinsics=", 28)) {
if (argv[0][28] == '\0')
PRINT_HELP_AND_EXIT();
option.builtin_intrinsics = argv[0] + 28;
}
else if (!strcmp(argv[0], "--disable-llvm-lto")) {
option.disable_llvm_lto = true;
}