blob: 8c50a7377c1098f06a35a3fd840943da428ea22b [file]
From: rive
Subject: [PATCH 0046] artifacts say which guard their unchecked code needs
Code compiled without bounds checks relied on the runtime reserving 8 GB per
memory, the most a wasm32 index plus a 32 bit offset can reach. wamrc now
leaves unchecked only an access whose offset and size fit a guard past 4 GB,
64 KB unless --rive-hw-guard says otherwise, and checks the end of any other
access against that reservation, a compare with a constant. Every artifact
records whether its code checks bounds and, if not, the guard it needs; the
loader refuses an artifact that records neither, and a guard other than the
runtime's. The artifact now also decides whether its traps unwind through the
guard page, which the embedder used to set.
diff --git a/core/iwasm/aot/aot_loader.c b/core/iwasm/aot/aot_loader.c
index 78dde4a..822128d 100644
--- a/core/iwasm/aot/aot_loader.c
+++ b/core/iwasm/aot/aot_loader.c
@@ -519,6 +519,43 @@ check_feature_flags(char *error_buf, uint32 error_buf_size,
return true;
}
+/* rive patch: code that skips bounds checks is safe only over the
+ reservation it was compiled for */
+static bool
+check_rive_bounds(AOTModule *module, uint64 bounds, char *error_buf,
+ uint32 error_buf_size)
+{
+ uint64 guard =
+ bounds & ~(RIVE_AOT_BOUNDS_KNOWN | RIVE_AOT_BOUNDS_UNCHECKED);
+
+ if (!(bounds & RIVE_AOT_BOUNDS_KNOWN)) {
+ set_error_buf(error_buf, error_buf_size,
+ "artifact records nothing about its bounds; compile it "
+ "again with a current wamrc");
+ return false;
+ }
+ if (!(bounds & RIVE_AOT_BOUNDS_UNCHECKED))
+ return true;
+#ifdef OS_ENABLE_HW_BOUND_CHECK
+ if (guard == WASM_RIVE_HW_GUARD_SIZE) {
+ module->rive_hw_bounds = true;
+ return true;
+ }
+ set_error_buf_v(error_buf, error_buf_size,
+ "artifact skips bounds checks within a %" PRIu64
+ " byte guard, this runtime reserves %u; compile it again "
+ "with a current wamrc",
+ guard, (uint32)WASM_RIVE_HW_GUARD_SIZE);
+#else
+ (void)module;
+ (void)guard;
+ set_error_buf(error_buf, error_buf_size,
+ "artifact skips bounds checks, which this build cannot "
+ "trap; compile it with --bounds-checks=1");
+#endif
+ return false;
+}
+
#if WASM_ENABLE_GC != 0
static WASMRefType *
reftype_set_insert(HashMap *ref_type_set, const WASMRefType *ref_type,
@@ -550,7 +587,7 @@ load_target_info_section(const uint8 *buf, const uint8 *buf_end,
read_uint32(p, p_end, target_info.e_version);
read_uint32(p, p_end, target_info.e_flags);
read_uint64(p, p_end, target_info.feature_flags);
- read_uint64(p, p_end, target_info.reserved);
+ read_uint64(p, p_end, target_info.rive_bounds);
read_byte_array(p, p_end, target_info.arch, sizeof(target_info.arch));
if (target_info.arch[sizeof(target_info.arch) - 1] != '\0') {
@@ -620,6 +657,11 @@ load_target_info_section(const uint8 *buf, const uint8 *buf_end,
module->feature_flags = target_info.feature_flags;
#endif
+ if (!check_rive_bounds(module, target_info.rive_bounds, error_buf,
+ error_buf_size)) {
+ return false;
+ }
+
/* Finally, check feature flags */
return check_feature_flags(error_buf, error_buf_size,
target_info.feature_flags);
diff --git a/core/iwasm/aot/aot_runtime.h b/core/iwasm/aot/aot_runtime.h
index ce6bf62..a94b5ce 100644
--- a/core/iwasm/aot/aot_runtime.h
+++ b/core/iwasm/aot/aot_runtime.h
@@ -36,6 +36,12 @@ extern "C" {
#define WASM_FEATURE_FRAME_PER_FUNCTION (1 << 12)
#define WASM_FEATURE_FRAME_NO_FUNC_IDX (1 << 13)
+/* rive: target info's rive_bounds says how the code bounds memory accesses;
+ artifacts from before it carry 0 there and are refused */
+#define RIVE_AOT_BOUNDS_KNOWN ((uint64)1 << 63)
+/* with the guard past 4 GB that unchecked accesses reach in the low bits */
+#define RIVE_AOT_BOUNDS_UNCHECKED ((uint64)1 << 62)
+
typedef enum AOTSectionType {
AOT_SECTION_TYPE_TARGET_INFO = 0,
AOT_SECTION_TYPE_INIT_DATA = 1,
@@ -349,11 +355,8 @@ typedef struct AOTModule {
/* Whether the underlying wasm binary buffer can be freed */
bool is_binary_freeable;
- /* rive patch: artifact compiled without sw bounds checks, so exceptions
- unwind through the exce_check guard page; sw artifacts unwind by
- checked returns and must never take that longjmp. Set by the embedder
- via wasm_runtime_set_module_hw_bounds; the file format records
- nothing. */
+ /* rive patch: the artifact says its code skips bounds checks, so its
+ memories reserve the guarded range and traps unwind through it */
bool rive_hw_bounds;
/* `.data` sections merged into one mmaped to reduce the tlb cache miss */
@@ -392,8 +395,8 @@ typedef struct AOTTargetInfo {
uint32 e_flags;
/* Specify wasm features supported */
uint64 feature_flags;
- /* Reserved */
- uint64 reserved;
+ /* rive: RIVE_AOT_BOUNDS_* */
+ uint64 rive_bounds;
/* Arch name */
char arch[16];
} AOTTargetInfo;
diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c
index 434271d..182d6fd 100644
--- a/core/iwasm/common/wasm_runtime_common.c
+++ b/core/iwasm/common/wasm_runtime_common.c
@@ -2367,17 +2367,15 @@ wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env,
}
#endif
-void
-wasm_runtime_set_module_hw_bounds(WASMModuleCommon *module, bool hw_bounds)
+bool
+wasm_runtime_rive_module_hw_bounds(WASMModuleCommon *module)
{
#if WASM_ENABLE_AOT != 0
- if (module && module->module_type == Wasm_Module_AoT) {
- ((AOTModule *)module)->rive_hw_bounds = hw_bounds;
- }
-#else
- (void)module;
+ if (module && module->module_type == Wasm_Module_AoT)
+ return ((AOTModule *)module)->rive_hw_bounds;
#endif
- (void)hw_bounds;
+ (void)module;
+ return false;
}
WASMFuncType *
diff --git a/core/iwasm/compilation/aot_emit_aot_file.c b/core/iwasm/compilation/aot_emit_aot_file.c
index 1274930..e8f994a 100644
--- a/core/iwasm/compilation/aot_emit_aot_file.c
+++ b/core/iwasm/compilation/aot_emit_aot_file.c
@@ -1745,7 +1745,7 @@ aot_emit_target_info_section(uint8 *buf, uint8 *buf_end, uint32 *p_offset,
EMIT_U32(target_info->e_version);
EMIT_U32(target_info->e_flags);
EMIT_U64(target_info->feature_flags);
- EMIT_U64(target_info->reserved);
+ EMIT_U64(target_info->rive_bounds);
EMIT_BUF(target_info->arch, sizeof(target_info->arch));
if (offset - *p_offset != section_size + sizeof(uint32) * 2) {
@@ -4513,6 +4513,11 @@ aot_obj_data_create(AOTCompContext *comp_ctx)
if (!comp_ctx->call_stack_features.func_idx) {
obj_data->target_info.feature_flags |= WASM_FEATURE_FRAME_NO_FUNC_IDX;
}
+ obj_data->target_info.rive_bounds = comp_ctx->enable_bound_check
+ ? RIVE_AOT_BOUNDS_KNOWN
+ : RIVE_AOT_BOUNDS_KNOWN
+ | RIVE_AOT_BOUNDS_UNCHECKED
+ | comp_ctx->rive_hw_guard;
bh_print_time("Begin to resolve object file info");
diff --git a/core/iwasm/compilation/aot_emit_memory.c b/core/iwasm/compilation/aot_emit_memory.c
index 0f42df5..2c80243 100644
--- a/core/iwasm/compilation/aot_emit_memory.c
+++ b/core/iwasm/compilation/aot_emit_memory.c
@@ -832,6 +832,25 @@ aot_check_memory_overflow(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx,
}
}
+ /* rive patch: the runtime reserves only the guard past 4 GB, so an
+ unchecked access whose offset could reach beyond it checks its end */
+ if (!comp_ctx->enable_bound_check && is_target_64bit && !is_memory64
+ && (uint64)offset + bytes > comp_ctx->rive_hw_guard) {
+ LLVMValueRef reserve_bound =
+ I64_CONST(4 * (uint64)BH_GB + comp_ctx->rive_hw_guard - bytes);
+
+ CHECK_LLVM_CONST(reserve_bound);
+ BUILD_ICMP(LLVMIntUGT, offset1, reserve_bound, cmp, "cmp_reserve");
+ ADD_BASIC_BLOCK(check_succ, "check_reserve_succ");
+ LLVMMoveBasicBlockAfter(check_succ, block_curr);
+ if (!aot_emit_exception(comp_ctx, func_ctx,
+ EXCE_OUT_OF_BOUNDS_MEMORY_ACCESS, true, cmp,
+ check_succ)) {
+ goto fail;
+ }
+ SET_BUILD_POS(check_succ);
+ }
+
if (!enable_segue) {
/* maddr = mem_base_addr + offset1 */
if (!(maddr =
diff --git a/core/iwasm/compilation/aot_llvm.c b/core/iwasm/compilation/aot_llvm.c
index f419073..92cb45a 100644
--- a/core/iwasm/compilation/aot_llvm.c
+++ b/core/iwasm/compilation/aot_llvm.c
@@ -2958,6 +2958,8 @@ aot_create_comp_context(const AOTCompData *comp_data, aot_comp_option_t option)
if (option->rive_interrupt)
comp_ctx->rive_interrupt = true;
+ comp_ctx->rive_hw_guard = option->rive_hw_guard;
+
comp_ctx->tune_cpu = option->tune_cpu;
if (option->enable_tail_call)
diff --git a/core/iwasm/compilation/aot_llvm.h b/core/iwasm/compilation/aot_llvm.h
index d0adc55..aac6a6f 100644
--- a/core/iwasm/compilation/aot_llvm.h
+++ b/core/iwasm/compilation/aot_llvm.h
@@ -451,6 +451,9 @@ typedef struct AOTCompContext {
/* rive: loop back-edges trap on the host watchdog's terminate flag */
bool rive_interrupt;
+ /* rive: without bounds checks, the reach past 4 GB left unchecked */
+ uint64 rive_hw_guard;
+
/* rive: tune-cpu attribute for every function, or NULL */
const char *tune_cpu;
diff --git a/core/iwasm/include/aot_comp_option.h b/core/iwasm/include/aot_comp_option.h
index 95ee3da..018d8c3 100644
--- a/core/iwasm/include/aot_comp_option.h
+++ b/core/iwasm/include/aot_comp_option.h
@@ -99,6 +99,8 @@ typedef struct AOTCompOption {
const char *builtin_intrinsics;
/* rive: loop back-edges trap on the host watchdog's terminate flag */
bool rive_interrupt;
+ /* rive: without bounds checks, the reach past 4 GB left unchecked */
+ uint64_t rive_hw_guard;
/* rive: scheduling model for every function, leaving the cpu's
instruction set baseline alone */
const char *tune_cpu;
diff --git a/core/iwasm/include/wasm_export.h b/core/iwasm/include/wasm_export.h
index 3c08264..68dfbdd 100644
--- a/core/iwasm/include/wasm_export.h
+++ b/core/iwasm/include/wasm_export.h
@@ -2333,12 +2333,13 @@ wasm_runtime_set_module_name(wasm_module_t module, const char *name,
WASM_RUNTIME_API_EXTERN const char *
wasm_runtime_get_module_name(wasm_module_t module);
-/* rive patch: mark an AOT module as compiled without sw bounds checks
- (wamrc --bounds-checks=0) so runtime exceptions unwind through the hw
- trap guard page. Default false: sw artifacts unwind by checked returns.
- The AOT file format records nothing, so the embedder must know. */
-WASM_RUNTIME_API_EXTERN void
-wasm_runtime_set_module_hw_bounds(wasm_module_t module, bool hw_bounds);
+/* rive: a hardware bounds memory reserves 4 GB and this, and compiled code
+ checks only an access whose offset and size pass it */
+#define WASM_RIVE_HW_GUARD_SIZE (64 * 1024)
+
+/* rive: whether module's code skips bounds checks, as its artifact says */
+WASM_RUNTIME_API_EXTERN bool
+wasm_runtime_rive_module_hw_bounds(wasm_module_t module);
/*
* wasm_runtime_detect_native_stack_overflow
diff --git a/wamr-compiler/main.c b/wamr-compiler/main.c
index 18351e6..1a53a01 100644
--- a/wamr-compiler/main.c
+++ b/wamr-compiler/main.c
@@ -148,6 +148,9 @@ print_help()
printf(" CAVEAT: --bounds-checks=0 enables some optimizations\n");
printf(" which make the compiled AOT module incompatible\n");
printf(" with a runtime without the hardware bounds checks.\n");
+ printf(" --rive-hw-guard=<bytes> With --bounds-checks=0, check only accesses whose offset and size\n");
+ printf(" pass this guard after 4 GB, which the runtime reserves (default %u)\n",
+ (unsigned)WASM_RIVE_HW_GUARD_SIZE);
printf(" --stack-bounds-checks=1/0 Enable or disable the bounds checks for native stack:\n");
printf(" if the option isn't set, the status is same as `--bounds-check`,\n");
printf(" if the option is set, the status is same as the option value\n");
@@ -420,6 +423,7 @@ main(int argc, char *argv[])
option.bounds_checks = 2;
/* default value, enable or disable depends on the platform */
option.stack_bounds_checks = 2;
+ option.rive_hw_guard = WASM_RIVE_HW_GUARD_SIZE;
option.enable_simd = true;
option.enable_aux_stack_check = true;
option.enable_bulk_memory = true;
@@ -503,6 +507,13 @@ main(int argc, char *argv[])
else if (!strncmp(argv[0], "--bounds-checks=", 16)) {
option.bounds_checks = (atoi(argv[0] + 16) == 1) ? 1 : 0;
}
+ else if (!strncmp(argv[0], "--rive-hw-guard=", 16)) {
+ char *end;
+ option.rive_hw_guard = strtoull(argv[0] + 16, &end, 10);
+ if (end == argv[0] + 16 || *end != '\0'
+ || option.rive_hw_guard > 4 * (uint64)BH_GB)
+ PRINT_HELP_AND_EXIT();
+ }
else if (!strncmp(argv[0], "--stack-bounds-checks=", 22)) {
option.stack_bounds_checks = (atoi(argv[0] + 22) == 1) ? 1 : 0;
}