mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 13:36:13 +03:00
LibWasm: Tighten validation algorithm
The big improvement included in this commit is stack height mismatch validation. There are other minor improvements included (related to the validation algorithm). The method of supporting stack polymorphism has changed to be more like the spec, which was necessary for confidently handling stack height mismatches. See: https://webassembly.github.io/spec/core/appendix/algorithm.html
This commit is contained in:
parent
8217a77728
commit
6e419db26c
Notes:
sideshowbarker
2024-07-18 04:38:32 +09:00
Author: https://github.com/dzfrias Commit: https://github.com/LadybirdBrowser/ladybird/commit/6e419db26c Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/147 Reviewed-by: https://github.com/alimpfard ✅
@ -233,6 +233,8 @@ ErrorOr<void, ValidationError> Validator::validate(ElementSection const& section
|
||||
}));
|
||||
|
||||
for (auto& expression : segment.init) {
|
||||
if (expression.instructions().is_empty())
|
||||
continue;
|
||||
auto result = TRY(validate(expression, { segment.type }));
|
||||
if (!result.is_constant)
|
||||
return Errors::invalid("element initializer"sv);
|
||||
@ -291,7 +293,9 @@ ErrorOr<void, ValidationError> Validator::validate(CodeSection const& section)
|
||||
|
||||
function_validator.m_frames.empend(function_type, FrameKind::Function, (size_t)0);
|
||||
|
||||
TRY(function_validator.validate(function.body(), function_type.results()));
|
||||
auto results = TRY(function_validator.validate(function.body(), function_type.results()));
|
||||
if (results.result_types.size() != function_type.results().size())
|
||||
return Errors::invalid("function result"sv, function_type.results(), results.result_types);
|
||||
}
|
||||
|
||||
return {};
|
||||
@ -384,176 +388,151 @@ VALIDATE_INSTRUCTION(f64_const)
|
||||
// https://webassembly.github.io/spec/core/bikeshed/#-tmathsfhrefsyntax-unopmathitunop
|
||||
VALIDATE_INSTRUCTION(i32_clz)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::I32) });
|
||||
TRY(stack.take_and_put<ValueType::I32>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i32_ctz)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::I32) });
|
||||
TRY(stack.take_and_put<ValueType::I32>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i32_popcnt)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::I32) });
|
||||
TRY(stack.take_and_put<ValueType::I32>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_clz)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::I64) });
|
||||
TRY(stack.take_and_put<ValueType::I64>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_ctz)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::I64) });
|
||||
TRY(stack.take_and_put<ValueType::I64>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_popcnt)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::I64) });
|
||||
TRY(stack.take_and_put<ValueType::I64>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f32_abs)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F32) });
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::F32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f32_neg)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F32) });
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::F32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f32_sqrt)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F32) });
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::F32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f32_ceil)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F32) });
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::F32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f32_floor)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F32) });
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::F32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f32_trunc)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F32) });
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::F32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f32_nearest)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F32))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F32) });
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::F32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f64_abs)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F32) });
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::F64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f64_neg)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F64) });
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::F64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f64_sqrt)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F64) });
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::F64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f64_ceil)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F64) });
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::F64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f64_floor)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F64) });
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::F64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f64_trunc)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F64) });
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::F64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f64_nearest)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::F64))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::F64) });
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::F64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i32_extend16_s)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::I32) });
|
||||
TRY(stack.take_and_put<ValueType::I32>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i32_extend8_s)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I32))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::I32) });
|
||||
TRY(stack.take_and_put<ValueType::I32>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_extend32_s)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::I64) });
|
||||
TRY(stack.take_and_put<ValueType::I64>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_extend16_s)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::I64) });
|
||||
TRY(stack.take_and_put<ValueType::I64>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_extend8_s)
|
||||
{
|
||||
if (stack.is_empty() || !stack.last().is_of_kind(ValueType::I64))
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::I64) });
|
||||
TRY(stack.take_and_put<ValueType::I64>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -1109,232 +1088,199 @@ VALIDATE_INSTRUCTION(f64_ge)
|
||||
// https://webassembly.github.io/spec/core/bikeshed/#-t_2mathsfhrefsyntax-cvtopmathitcvtopmathsf_t_1mathsf_hrefsyntax-sxmathitsx
|
||||
VALIDATE_INSTRUCTION(i32_wrap_i64)
|
||||
{
|
||||
TRY(stack.take<ValueType::I64>());
|
||||
stack.append(ValueType(ValueType::I32));
|
||||
TRY(stack.take_and_put<ValueType::I64>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_extend_si32)
|
||||
{
|
||||
TRY(stack.take<ValueType::I32>());
|
||||
stack.append(ValueType(ValueType::I64));
|
||||
TRY(stack.take_and_put<ValueType::I32>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_extend_ui32)
|
||||
{
|
||||
TRY(stack.take<ValueType::I32>());
|
||||
stack.append(ValueType(ValueType::I64));
|
||||
TRY(stack.take_and_put<ValueType::I32>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i32_trunc_sf32)
|
||||
{
|
||||
TRY(stack.take<ValueType::F32>());
|
||||
stack.append(ValueType(ValueType::I32));
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i32_trunc_uf32)
|
||||
{
|
||||
TRY(stack.take<ValueType::F32>());
|
||||
stack.append(ValueType(ValueType::I32));
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i32_trunc_sf64)
|
||||
{
|
||||
TRY(stack.take<ValueType::F64>());
|
||||
stack.append(ValueType(ValueType::I32));
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i32_trunc_uf64)
|
||||
{
|
||||
TRY(stack.take<ValueType::F64>());
|
||||
stack.append(ValueType(ValueType::I32));
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_trunc_sf32)
|
||||
{
|
||||
TRY(stack.take<ValueType::F32>());
|
||||
stack.append(ValueType(ValueType::I64));
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_trunc_uf32)
|
||||
{
|
||||
TRY(stack.take<ValueType::F32>());
|
||||
stack.append(ValueType(ValueType::I64));
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_trunc_sf64)
|
||||
{
|
||||
TRY(stack.take<ValueType::F64>());
|
||||
stack.append(ValueType(ValueType::I64));
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_trunc_uf64)
|
||||
{
|
||||
TRY(stack.take<ValueType::F64>());
|
||||
stack.append(ValueType(ValueType::I64));
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i32_trunc_sat_f32_s)
|
||||
{
|
||||
TRY(stack.take<ValueType::F32>());
|
||||
stack.append(ValueType(ValueType::I32));
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i32_trunc_sat_f32_u)
|
||||
{
|
||||
TRY(stack.take<ValueType::F32>());
|
||||
stack.append(ValueType(ValueType::I32));
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i32_trunc_sat_f64_s)
|
||||
{
|
||||
TRY(stack.take<ValueType::F64>());
|
||||
stack.append(ValueType(ValueType::I32));
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i32_trunc_sat_f64_u)
|
||||
{
|
||||
TRY(stack.take<ValueType::F64>());
|
||||
stack.append(ValueType(ValueType::I32));
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_trunc_sat_f32_s)
|
||||
{
|
||||
TRY(stack.take<ValueType::F32>());
|
||||
stack.append(ValueType(ValueType::I64));
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_trunc_sat_f32_u)
|
||||
{
|
||||
TRY(stack.take<ValueType::F32>());
|
||||
stack.append(ValueType(ValueType::I64));
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_trunc_sat_f64_s)
|
||||
{
|
||||
TRY(stack.take<ValueType::F64>());
|
||||
stack.append(ValueType(ValueType::I64));
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_trunc_sat_f64_u)
|
||||
{
|
||||
TRY(stack.take<ValueType::F64>());
|
||||
stack.append(ValueType(ValueType::I64));
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f32_convert_si32)
|
||||
{
|
||||
TRY(stack.take<ValueType::I32>());
|
||||
stack.append(ValueType(ValueType::F32));
|
||||
TRY(stack.take_and_put<ValueType::I32>(ValueType::F32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f32_convert_ui32)
|
||||
{
|
||||
TRY(stack.take<ValueType::I32>());
|
||||
stack.append(ValueType(ValueType::F32));
|
||||
TRY(stack.take_and_put<ValueType::I32>(ValueType::F32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f32_convert_si64)
|
||||
{
|
||||
TRY(stack.take<ValueType::I64>());
|
||||
stack.append(ValueType(ValueType::F32));
|
||||
TRY(stack.take_and_put<ValueType::I64>(ValueType::F32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f32_convert_ui64)
|
||||
{
|
||||
TRY(stack.take<ValueType::I64>());
|
||||
stack.append(ValueType(ValueType::F32));
|
||||
TRY(stack.take_and_put<ValueType::I64>(ValueType::F32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f64_convert_si32)
|
||||
{
|
||||
TRY(stack.take<ValueType::I32>());
|
||||
stack.append(ValueType(ValueType::F64));
|
||||
TRY(stack.take_and_put<ValueType::I32>(ValueType::F64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f64_convert_ui32)
|
||||
{
|
||||
TRY(stack.take<ValueType::I32>());
|
||||
stack.append(ValueType(ValueType::F64));
|
||||
TRY(stack.take_and_put<ValueType::I32>(ValueType::F64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f64_convert_si64)
|
||||
{
|
||||
TRY(stack.take<ValueType::I64>());
|
||||
stack.append(ValueType(ValueType::F64));
|
||||
TRY(stack.take_and_put<ValueType::I64>(ValueType::F64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f64_convert_ui64)
|
||||
{
|
||||
TRY(stack.take<ValueType::I64>());
|
||||
stack.append(ValueType(ValueType::F64));
|
||||
TRY(stack.take_and_put<ValueType::I64>(ValueType::F64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f32_demote_f64)
|
||||
{
|
||||
TRY(stack.take<ValueType::F64>());
|
||||
stack.append(ValueType(ValueType::F32));
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::F32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f64_promote_f32)
|
||||
{
|
||||
TRY(stack.take<ValueType::F32>());
|
||||
stack.append(ValueType(ValueType::F64));
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::F64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f32_reinterpret_i32)
|
||||
{
|
||||
TRY(stack.take<ValueType::I32>());
|
||||
stack.append(ValueType(ValueType::F32));
|
||||
TRY(stack.take_and_put<ValueType::I32>(ValueType::F32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(f64_reinterpret_i64)
|
||||
{
|
||||
TRY(stack.take<ValueType::I64>());
|
||||
stack.append(ValueType(ValueType::F64));
|
||||
TRY(stack.take_and_put<ValueType::I64>(ValueType::F64));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i32_reinterpret_f32)
|
||||
{
|
||||
TRY(stack.take<ValueType::F32>());
|
||||
stack.append(ValueType(ValueType::I32));
|
||||
TRY(stack.take_and_put<ValueType::F32>(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(i64_reinterpret_f64)
|
||||
{
|
||||
TRY(stack.take<ValueType::F64>());
|
||||
stack.append(ValueType(ValueType::I64));
|
||||
TRY(stack.take_and_put<ValueType::F64>(ValueType::I64));
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -1352,7 +1298,7 @@ VALIDATE_INSTRUCTION(ref_is_null)
|
||||
if (stack.is_empty() || !stack.last().is_reference())
|
||||
return Errors::invalid_stack_state(stack, Tuple { "reference" });
|
||||
|
||||
stack.take_last();
|
||||
TRY(stack.take_last());
|
||||
stack.append(ValueType(ValueType::I32));
|
||||
return {};
|
||||
}
|
||||
@ -1373,26 +1319,21 @@ VALIDATE_INSTRUCTION(ref_func)
|
||||
// https://webassembly.github.io/spec/core/bikeshed/#parametric-instructions%E2%91%A2
|
||||
VALIDATE_INSTRUCTION(drop)
|
||||
{
|
||||
if (stack.is_empty())
|
||||
return Errors::invalid_stack_state(stack, Tuple { "any" });
|
||||
stack.take_last();
|
||||
TRY(stack.take_last());
|
||||
return {};
|
||||
}
|
||||
|
||||
VALIDATE_INSTRUCTION(select)
|
||||
{
|
||||
if (stack.size() < 3)
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::I32), "any", "any" });
|
||||
TRY(stack.take<ValueType::I32>());
|
||||
auto arg0_type = TRY(stack.take_last());
|
||||
auto arg1_type = TRY(stack.take_last());
|
||||
|
||||
auto index_type = stack.take_last();
|
||||
auto arg0_type = stack.take_last();
|
||||
auto& arg1_type = stack.last();
|
||||
if (!index_type.is_of_kind(ValueType::I32))
|
||||
return Errors::invalid("select index type"sv, ValueType(ValueType::I32), index_type);
|
||||
|
||||
if (arg0_type != arg1_type)
|
||||
if (arg0_type != arg1_type || arg0_type.concrete_type.is_reference() || arg1_type.concrete_type.is_reference())
|
||||
return Errors::invalid("select argument types"sv, Vector { arg0_type, arg0_type }, Vector { arg0_type, arg1_type });
|
||||
|
||||
stack.append(arg0_type.is_known ? arg0_type : arg1_type);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -1402,18 +1343,15 @@ VALIDATE_INSTRUCTION(select_typed)
|
||||
if (required_types.size() != 1)
|
||||
return Errors::invalid("select types"sv, "exactly one type"sv, required_types);
|
||||
|
||||
if (stack.size() < 3)
|
||||
return Errors::invalid_stack_state(stack, Tuple { ValueType(ValueType::I32), required_types.first(), required_types.first() });
|
||||
|
||||
auto index_type = stack.take_last();
|
||||
auto arg0_type = stack.take_last();
|
||||
auto& arg1_type = stack.last();
|
||||
if (!index_type.is_of_kind(ValueType::I32))
|
||||
return Errors::invalid("select index type"sv, ValueType(ValueType::I32), index_type);
|
||||
TRY(stack.take<ValueType::I32>());
|
||||
auto arg0_type = TRY(stack.take_last());
|
||||
auto arg1_type = TRY(stack.take_last());
|
||||
|
||||
if (arg0_type != arg1_type || arg0_type != required_types.first())
|
||||
return Errors::invalid("select argument types"sv, Vector { required_types.first(), required_types.first() }, Vector { arg0_type, arg1_type });
|
||||
|
||||
stack.append(arg0_type.is_known ? arg0_type : arg1_type);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -1982,7 +1920,8 @@ VALIDATE_INSTRUCTION(nop)
|
||||
VALIDATE_INSTRUCTION(unreachable)
|
||||
{
|
||||
// https://webassembly.github.io/spec/core/bikeshed/#polymorphism
|
||||
stack.append(StackEntry());
|
||||
m_frames.last().unreachable = true;
|
||||
stack.resize(m_frames.last().initial_size);
|
||||
|
||||
return {};
|
||||
}
|
||||
@ -1993,14 +1932,18 @@ VALIDATE_INSTRUCTION(structured_end)
|
||||
if (m_frames.is_empty())
|
||||
return Errors::invalid("usage of structured end"sv);
|
||||
|
||||
auto last_frame = m_frames.take_last();
|
||||
auto& last_frame = m_frames.last();
|
||||
|
||||
auto& results = last_frame.type.results();
|
||||
for (size_t i = 1; i <= results.size(); ++i)
|
||||
TRY(stack.take(results[results.size() - i]));
|
||||
|
||||
if (stack.size() != last_frame.initial_size)
|
||||
return Errors::stack_height_mismatch(stack, last_frame.initial_size);
|
||||
|
||||
for (auto& result : results)
|
||||
stack.append(result);
|
||||
m_frames.take_last();
|
||||
|
||||
return {};
|
||||
}
|
||||
@ -2014,14 +1957,18 @@ VALIDATE_INSTRUCTION(structured_else)
|
||||
if (m_frames.last().kind != FrameKind::If)
|
||||
return Errors::invalid("usage of structured else"sv);
|
||||
|
||||
auto frame = m_frames.take_last();
|
||||
auto& frame = m_frames.last();
|
||||
auto& block_type = frame.type;
|
||||
auto& results = block_type.results();
|
||||
|
||||
for (size_t i = 1; i <= results.size(); ++i)
|
||||
TRY(stack.take(results[results.size() - i]));
|
||||
|
||||
m_frames.empend(block_type, FrameKind::Else, stack.actual_size());
|
||||
if (stack.size() != frame.initial_size)
|
||||
return Errors::stack_height_mismatch(stack, frame.initial_size);
|
||||
|
||||
frame.kind = FrameKind::Else;
|
||||
frame.unreachable = false;
|
||||
for (auto& parameter : block_type.parameters())
|
||||
stack.append(parameter);
|
||||
|
||||
@ -2037,7 +1984,7 @@ VALIDATE_INSTRUCTION(block)
|
||||
for (size_t i = 1; i <= parameters.size(); ++i)
|
||||
TRY(stack.take(parameters[parameters.size() - i]));
|
||||
|
||||
m_frames.empend(block_type, FrameKind::Block, stack.actual_size());
|
||||
m_frames.empend(block_type, FrameKind::Block, stack.size());
|
||||
for (auto& parameter : parameters)
|
||||
stack.append(parameter);
|
||||
|
||||
@ -2053,7 +2000,7 @@ VALIDATE_INSTRUCTION(loop)
|
||||
for (size_t i = 1; i <= parameters.size(); ++i)
|
||||
TRY(stack.take(parameters[parameters.size() - i]));
|
||||
|
||||
m_frames.empend(block_type, FrameKind::Loop, stack.actual_size());
|
||||
m_frames.empend(block_type, FrameKind::Loop, stack.size());
|
||||
for (auto& parameter : parameters)
|
||||
stack.append(parameter);
|
||||
|
||||
@ -2073,7 +2020,7 @@ VALIDATE_INSTRUCTION(if_)
|
||||
for (size_t i = 1; i <= parameters.size(); ++i)
|
||||
TRY(stack.take(parameters[parameters.size() - i]));
|
||||
|
||||
m_frames.empend(block_type, FrameKind::If, stack.actual_size());
|
||||
m_frames.empend(block_type, FrameKind::If, stack.size());
|
||||
for (auto& parameter : parameters)
|
||||
stack.append(parameter);
|
||||
|
||||
@ -2089,7 +2036,8 @@ VALIDATE_INSTRUCTION(br)
|
||||
for (size_t i = 1; i <= type.size(); ++i)
|
||||
TRY(stack.take(type[type.size() - i]));
|
||||
|
||||
stack.append(StackEntry());
|
||||
m_frames.last().unreachable = true;
|
||||
stack.resize(m_frames.last().initial_size);
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -2130,15 +2078,17 @@ VALIDATE_INSTRUCTION(br_table)
|
||||
auto& default_types = m_frames[(m_frames.size() - 1) - args.default_.value()].labels();
|
||||
auto arity = default_types.size();
|
||||
|
||||
auto stack_snapshot = stack;
|
||||
auto stack_to_check = stack_snapshot;
|
||||
for (auto& label : args.labels) {
|
||||
auto& label_types = m_frames[(m_frames.size() - 1) - label.value()].labels();
|
||||
if (label_types.size() != arity)
|
||||
return Errors::invalid("br_table label arity mismatch"sv);
|
||||
for (size_t i = 0; i < arity; ++i)
|
||||
TRY(stack_to_check.take(label_types[label_types.size() - i - 1]));
|
||||
stack_to_check = stack_snapshot;
|
||||
Vector<StackEntry> popped {};
|
||||
for (size_t i = 0; i < arity; ++i) {
|
||||
auto stack_entry = TRY(stack.take(label_types[label_types.size() - i - 1]));
|
||||
popped.append(stack_entry);
|
||||
}
|
||||
for (auto popped_type : popped.in_reverse())
|
||||
stack.append(popped_type);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < arity; ++i) {
|
||||
@ -2146,7 +2096,8 @@ VALIDATE_INSTRUCTION(br_table)
|
||||
TRY((stack.take(expected)));
|
||||
}
|
||||
|
||||
stack.append(StackEntry());
|
||||
m_frames.last().unreachable = true;
|
||||
stack.resize(m_frames.last().initial_size);
|
||||
|
||||
return {};
|
||||
}
|
||||
@ -2157,7 +2108,8 @@ VALIDATE_INSTRUCTION(return_)
|
||||
for (size_t i = 0; i < return_types.size(); ++i)
|
||||
TRY((stack.take(return_types[return_types.size() - i - 1])));
|
||||
|
||||
stack.append(StackEntry());
|
||||
m_frames.last().unreachable = true;
|
||||
stack.resize(m_frames.last().initial_size);
|
||||
|
||||
return {};
|
||||
}
|
||||
@ -3777,7 +3729,9 @@ ErrorOr<void, ValidationError> Validator::validate(Instruction const& instructio
|
||||
|
||||
ErrorOr<Validator::ExpressionTypeResult, ValidationError> Validator::validate(Expression const& expression, Vector<ValueType> const& result_types)
|
||||
{
|
||||
Stack stack;
|
||||
if (m_frames.is_empty())
|
||||
m_frames.empend(FunctionType { {}, result_types }, FrameKind::Function, (size_t)0);
|
||||
auto stack = Stack(m_frames);
|
||||
bool is_constant_expression = true;
|
||||
|
||||
for (auto& instruction : expression.instructions()) {
|
||||
@ -3793,61 +3747,12 @@ ErrorOr<Validator::ExpressionTypeResult, ValidationError> Validator::validate(Ex
|
||||
|
||||
for (auto& type : result_types)
|
||||
stack.append(type);
|
||||
m_frames.take_last();
|
||||
VERIFY(m_frames.is_empty());
|
||||
|
||||
return ExpressionTypeResult { stack.release_vector(), is_constant_expression };
|
||||
}
|
||||
|
||||
bool Validator::Stack::operator==(Stack const& other) const
|
||||
{
|
||||
if (!m_did_insert_unknown_entry && !other.m_did_insert_unknown_entry)
|
||||
return static_cast<Vector<StackEntry> const&>(*this) == static_cast<Vector<StackEntry> const&>(other);
|
||||
|
||||
Optional<size_t> own_last_unknown_entry_index_from_end, other_last_unknown_entry_index_from_end;
|
||||
auto other_size = static_cast<Vector<StackEntry> const&>(other).size();
|
||||
auto own_size = Vector<StackEntry>::size();
|
||||
|
||||
for (size_t i = 0; i < own_size; ++i) {
|
||||
if (other_size <= i)
|
||||
break;
|
||||
|
||||
auto own_entry = at(own_size - i - 1);
|
||||
auto other_entry = other.at(other_size - i - 1);
|
||||
if (!own_entry.is_known) {
|
||||
own_last_unknown_entry_index_from_end = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!other_entry.is_known) {
|
||||
other_last_unknown_entry_index_from_end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!own_last_unknown_entry_index_from_end.has_value() && !other_last_unknown_entry_index_from_end.has_value()) {
|
||||
if (static_cast<Vector<StackEntry> const&>(other).is_empty() || Vector<StackEntry>::is_empty())
|
||||
return true;
|
||||
|
||||
dbgln("Equality check internal error between");
|
||||
dbgln("stack:");
|
||||
for (auto& entry : *this)
|
||||
dbgln("- {}", entry.is_known ? Wasm::ValueType::kind_name(entry.concrete_type.kind()) : "<unknown>");
|
||||
dbgln("and stack:");
|
||||
for (auto& entry : other)
|
||||
dbgln("- {}", entry.is_known ? Wasm::ValueType::kind_name(entry.concrete_type.kind()) : "<unknown>");
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
auto index_from_end = max(own_last_unknown_entry_index_from_end.value_or(0), other_last_unknown_entry_index_from_end.value_or(0));
|
||||
|
||||
for (size_t i = 0; i < index_from_end; ++i) {
|
||||
if (at(own_size - i - 1) != other.at(other_size - i - 1))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ByteString Validator::Errors::find_instruction_name(SourceLocation const& location)
|
||||
{
|
||||
auto index = location.function_name().find('<');
|
||||
|
@ -132,6 +132,27 @@ public:
|
||||
return Errors::invalid("TableIndex"sv);
|
||||
}
|
||||
|
||||
enum class FrameKind {
|
||||
Block,
|
||||
Loop,
|
||||
If,
|
||||
Else,
|
||||
Function,
|
||||
};
|
||||
|
||||
struct Frame {
|
||||
FunctionType type;
|
||||
FrameKind kind;
|
||||
size_t initial_size;
|
||||
// Stack polymorphism is handled with this field
|
||||
bool unreachable { false };
|
||||
|
||||
Vector<ValueType> const& labels() const
|
||||
{
|
||||
return kind != FrameKind::Loop ? type.results() : type.parameters();
|
||||
}
|
||||
};
|
||||
|
||||
// Instructions
|
||||
struct StackEntry {
|
||||
StackEntry(ValueType type)
|
||||
@ -181,66 +202,58 @@ public:
|
||||
friend struct AK::Formatter;
|
||||
|
||||
public:
|
||||
// The unknown entry will never be popped off, so we can safely use the original `is_empty`.
|
||||
Stack(Vector<Frame> const& frames)
|
||||
: m_frames(frames)
|
||||
{
|
||||
}
|
||||
|
||||
using Vector<StackEntry>::is_empty;
|
||||
using Vector<StackEntry>::last;
|
||||
using Vector<StackEntry>::at;
|
||||
using Vector<StackEntry>::size;
|
||||
using Vector<StackEntry>::resize;
|
||||
|
||||
StackEntry take_last()
|
||||
ErrorOr<StackEntry, ValidationError> take_last()
|
||||
{
|
||||
if (last().is_known)
|
||||
return Vector<StackEntry>::take_last();
|
||||
return last();
|
||||
if (size() == m_frames.last().initial_size && m_frames.last().unreachable)
|
||||
return StackEntry();
|
||||
if (size() == m_frames.last().initial_size)
|
||||
return Errors::invalid("stack state"sv, "<any>"sv, "<nothing>"sv);
|
||||
return Vector<StackEntry>::take_last();
|
||||
}
|
||||
void append(StackEntry entry)
|
||||
{
|
||||
if (!entry.is_known)
|
||||
m_did_insert_unknown_entry = true;
|
||||
Vector<StackEntry>::append(entry);
|
||||
}
|
||||
|
||||
ErrorOr<void, ValidationError> take(ValueType type, SourceLocation location = SourceLocation::current())
|
||||
ErrorOr<StackEntry, ValidationError> take(ValueType type, SourceLocation location = SourceLocation::current())
|
||||
{
|
||||
if (is_empty())
|
||||
return Errors::invalid("stack state"sv, type, "<nothing>"sv, location);
|
||||
|
||||
auto type_on_stack = take_last();
|
||||
auto type_on_stack = TRY(take_last());
|
||||
if (type_on_stack != type)
|
||||
return Errors::invalid("stack state"sv, type, type_on_stack, location);
|
||||
|
||||
return {};
|
||||
return type_on_stack;
|
||||
}
|
||||
|
||||
template<auto... kinds>
|
||||
ErrorOr<void, ValidationError> take(SourceLocation location = SourceLocation::current())
|
||||
{
|
||||
ErrorOr<void, ValidationError> result;
|
||||
if (((result = take(Wasm::ValueType(kinds), location)).is_error(), ...)) {
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
for (auto kind : { kinds... })
|
||||
TRY(take(Wasm::ValueType(kind), location));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<auto... kinds>
|
||||
ErrorOr<void, ValidationError> take_and_put(Wasm::ValueType::Kind kind, SourceLocation location = SourceLocation::current())
|
||||
{
|
||||
ErrorOr<void, ValidationError> result;
|
||||
if (((result = take(Wasm::ValueType(kinds), location)).is_error(), ...)) {
|
||||
return result;
|
||||
}
|
||||
TRY(take<kinds...>(location));
|
||||
append(Wasm::ValueType(kind));
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t actual_size() const { return Vector<StackEntry>::size(); }
|
||||
size_t size() const { return m_did_insert_unknown_entry ? static_cast<size_t>(-1) : actual_size(); }
|
||||
|
||||
Vector<StackEntry> release_vector() { return exchange(static_cast<Vector<StackEntry>&>(*this), Vector<StackEntry> {}); }
|
||||
|
||||
bool operator==(Stack const& other) const;
|
||||
|
||||
private:
|
||||
bool m_did_insert_unknown_entry { false };
|
||||
Vector<Frame> const& m_frames;
|
||||
};
|
||||
|
||||
struct ExpressionTypeResult {
|
||||
@ -286,6 +299,7 @@ private:
|
||||
|
||||
static ValidationError duplicate_export_name(StringView name) { return ByteString::formatted("Duplicate exported name '{}'", name); }
|
||||
static ValidationError multiple_start_sections() { return ByteString("Found multiple start sections"sv); }
|
||||
static ValidationError stack_height_mismatch(Stack const& stack, size_t expected_height) { return ByteString::formatted("Stack height mismatch, got {} but expected length {}", stack, expected_height); }
|
||||
|
||||
template<typename T, typename U, typename V>
|
||||
static ValidationError out_of_bounds(StringView name, V value, T min, U max) { return ByteString::formatted("Value {} for {} is out of bounds ({},{})", value, name, min, max); }
|
||||
@ -308,7 +322,7 @@ private:
|
||||
|
||||
builder.append("], but found [ "sv);
|
||||
|
||||
auto actual_size = stack.actual_size();
|
||||
auto actual_size = stack.size();
|
||||
for (size_t i = 1; i <= min(count, actual_size); ++i) {
|
||||
auto& entry = stack.at(actual_size - i);
|
||||
if (entry.is_known) {
|
||||
@ -326,34 +340,6 @@ private:
|
||||
static ByteString find_instruction_name(SourceLocation const&);
|
||||
};
|
||||
|
||||
struct BlockDetails {
|
||||
size_t initial_stack_size { 0 };
|
||||
struct IfDetails {
|
||||
Stack initial_stack;
|
||||
};
|
||||
Variant<IfDetails, Empty> details;
|
||||
};
|
||||
|
||||
enum class FrameKind {
|
||||
Block,
|
||||
Loop,
|
||||
If,
|
||||
Else,
|
||||
Function,
|
||||
};
|
||||
|
||||
struct Frame {
|
||||
FunctionType type;
|
||||
FrameKind kind;
|
||||
size_t init_size;
|
||||
bool unreachable { false };
|
||||
|
||||
Vector<ValueType> const& labels() const
|
||||
{
|
||||
return kind != FrameKind::Loop ? type.results() : type.parameters();
|
||||
}
|
||||
};
|
||||
|
||||
Context m_context;
|
||||
Vector<Frame> m_frames;
|
||||
COWVector<GlobalType> m_globals_without_internal_globals;
|
||||
|
Loading…
Reference in New Issue
Block a user