///|
pub fn diff(rt1 : RefType, rt2 : RefType) -> Result[RefType, String] {
let ht1 = rt1.get_heap_type()
let n1 = rt1.is_nullable()
let n2 = rt2.is_nullable()
// Result nullability: nullable only if rt1 was nullable AND rt2 was not
let null_out = n1 && !n2
Ok(RefType::new(null_out, ht1, exact=rt1.is_exact()))
}
///|
pub enum ValidationIssue {
FeaturePolicy(String)
TypeSection(String)
ImportSection(String)
FunctionSection(String)
TableSection(String)
MemorySection(String)
TagSection(String)
GlobalSection(String)
ElementSection(String)
DataSection(String)
DataCountSection(String)
StartSection(String)
ExportSection(String)
CodeSection(String)
NameSection(String)
CompilerFactsSection(String)
FunctionBody(String)
} derive(Eq, Debug)
///|
pub struct ValidationDiagnostic {
issue : ValidationIssue
func_idx : FuncIdx?
} derive(Eq, Debug)
///|
pub fn ValidationDiagnostic::new(
issue : ValidationIssue,
func_idx~ : FuncIdx? = None,
) -> ValidationDiagnostic {
ValidationDiagnostic::{ issue, func_idx, }
}
///|
pub fn validation_issue_message(issue : ValidationIssue) -> String {
match issue {
FeaturePolicy(msg)
| TypeSection(msg)
| ImportSection(msg)
| FunctionSection(msg)
| TableSection(msg)
| MemorySection(msg)
| TagSection(msg)
| GlobalSection(msg)
| ElementSection(msg)
| DataSection(msg)
| DataCountSection(msg)
| StartSection(msg)
| ExportSection(msg)
| NameSection(msg)
| CompilerFactsSection(msg)
| CodeSection(msg)
| FunctionBody(msg) => msg
}
}
///|
pub impl Show for ValidationIssue with fn output(self, logger) {
logger.write_string(validation_issue_message(self))
}
///|
pub impl Show for ValidationDiagnostic with fn output(self, logger) {
match self.func_idx {
Some(FuncIdx(i)) => {
logger.write_string("func[")
i.output(logger)
logger.write_string("]: ")
}
None => ()
}
logger.write_string(validation_issue_message(self.issue))
}
///|
pub suberror ValidationError {
Validation(ValidationDiagnostic)
} derive(Eq, Debug)
///|
pub impl Show for ValidationError with fn output(self, logger) {
match self {
Validation(diag) => diag.output(logger)
}
}
///|
pub fn validation_error_diagnostic(
err : ValidationError,
) -> ValidationDiagnostic {
match err {
Validation(diag) => diag
}
}
///|
pub fn validation_error_message(err : ValidationError) -> String {
let diag = validation_error_diagnostic(err)
validation_issue_message(diag.issue)
}
///|
pub fn validation_error_func_idx(err : ValidationError) -> FuncIdx? {
let diag = validation_error_diagnostic(err)
diag.func_idx
}
///|
priv struct ValidateHelperTimingSnapshot {
validate_func_body_us : UInt64
validate_func_body_calls : Int32
}
///|
priv struct ValidateHotspotEntry {
ordinal : Int32
body_ticks : UInt64
locals : Int32
top_instrs : Int32
}
///|
const VALIDATE_HOTSPOT_SUMMARY_LIMIT : Int32 = 8
///|
const VALIDATE_REF_FUNC_DECL_TRACE_HEARTBEAT : Int32 = 256
///|
let validate_helper_timing_validate_func_body_us : Ref[UInt64] = Ref::new(0UL)
///|
let validate_helper_timing_validate_func_body_calls : Ref[Int32] = Ref::new(0)
///|
let validate_hotspot_entries : Ref[Array[ValidateHotspotEntry]] = Ref::new([])
///|
let validate_phase_totals_ms : Ref[Map[String, UInt64]] = Ref::new(Map::new())
///|
let validate_phase_totals_calls : Ref[Map[String, Int32]] = Ref::new(Map::new())
///|
fn validate_trace_noop(_ : String) -> Unit {
()
}
///|
fn validate_trace_now_us() -> UInt64 {
trace_now_us()
}
///|
fn validate_trace_now_ms() -> UInt64 {
trace_now_ms()
}
///|
fn validate_trace_elapsed_ms(start_ms : UInt64) -> UInt64 {
trace_elapsed_ms(start_ms)
}
///|
fn validate_trace_elapsed_us_since(start_us : UInt64) -> UInt64 {
trace_elapsed_us_since(start_us)
}
///|
fn validate_trace_delta_us_to_ms(delta_us : UInt64) -> UInt64 {
trace_delta_us_to_ms(delta_us)
}
///|
fn validate_trace_log_enabled(ordinal : Int32, trace_all_funcs : Bool) -> Bool {
ignore(ordinal)
trace_all_funcs
}
///|
fn validate_trace_phase_start(
trace : (String) -> Unit,
phase : String,
enabled : Bool,
) -> UInt64 {
if !enabled {
return 0UL
}
let start_ms = validate_trace_now_ms()
trace("phase=\{phase}:start")
start_ms
}
///|
fn validate_trace_phase_done(
trace : (String) -> Unit,
phase : String,
start_ms : UInt64,
enabled : Bool,
) -> Unit {
if !enabled {
return
}
let elapsed_ms = validate_trace_elapsed_ms(start_ms)
validate_phase_timing_commit(phase, elapsed_ms)
trace("phase=\{phase}:done elapsed_ms=\{elapsed_ms}")
}
///|
fn validate_phase_timing_reset() -> Unit {
validate_phase_totals_ms.val = Map::new()
validate_phase_totals_calls.val = Map::new()
}
///|
fn validate_phase_timing_commit(phase : String, elapsed_ms : UInt64) -> Unit {
let totals_ms = validate_phase_totals_ms.val
let totals_calls = validate_phase_totals_calls.val
let current_ms = totals_ms.get(phase).unwrap_or(0UL)
let current_calls = totals_calls.get(phase).unwrap_or(0)
totals_ms.set(phase, current_ms + elapsed_ms)
totals_calls.set(phase, current_calls + 1)
}
///|
fn validate_phase_timing_total_ms(phase : String) -> UInt64 {
validate_phase_totals_ms.val.get(phase).unwrap_or(0UL)
}
///|
fn validate_phase_timing_total_calls(phase : String) -> Int32 {
validate_phase_totals_calls.val.get(phase).unwrap_or(0)
}
///|
fn validate_phase_totals_line() -> String {
let phases = [
"typesec", "importsec", "funcsec", "tablesec", "memsec", "tagsec", "globalsec",
"elemsec", "datasec", "datacnt", "datacnt_requirement", "startsec", "exportsec",
"ref_func_declarations", "codesec", "namesec",
]
let out = StringBuilder::new()
out.write_string("phase_totals")
for phase in phases {
let calls = validate_phase_timing_total_calls(phase)
if calls > 0 {
out.write_string(
" \{phase}_ms=\{validate_phase_timing_total_ms(phase)} \{phase}_calls=\{calls}",
)
}
}
out.to_string()
}
///|
fn validate_helper_timing_reset() -> Unit {
validate_helper_timing_validate_func_body_us.val = 0UL
validate_helper_timing_validate_func_body_calls.val = 0
}
///|
fn validate_helper_timing_commit(
enabled : Bool,
elapsed_us : Ref[UInt64],
calls : Ref[Int32],
start_us : UInt64,
) -> Unit {
if !enabled {
return
}
elapsed_us.val = elapsed_us.val + validate_trace_elapsed_us_since(start_us)
calls.val = calls.val + 1
}
///|
fn validate_helper_timing_snapshot() -> ValidateHelperTimingSnapshot {
{
validate_func_body_us: validate_helper_timing_validate_func_body_us.val,
validate_func_body_calls: validate_helper_timing_validate_func_body_calls.val,
}
}
///|
fn validate_helper_timing_zero() -> ValidateHelperTimingSnapshot {
{ validate_func_body_us: 0UL, validate_func_body_calls: 0, }
}
///|
fn validate_helper_timing_delta_u64(after : UInt64, before : UInt64) -> UInt64 {
if after >= before {
after - before
} else {
0UL
}
}
///|
fn validate_helper_timing_diff(
after : ValidateHelperTimingSnapshot,
before : ValidateHelperTimingSnapshot,
) -> ValidateHelperTimingSnapshot {
{
validate_func_body_us: validate_helper_timing_delta_u64(
after.validate_func_body_us,
before.validate_func_body_us,
),
validate_func_body_calls: after.validate_func_body_calls -
before.validate_func_body_calls,
}
}
///|
fn validate_helper_timing_has_nonzero(
snapshot : ValidateHelperTimingSnapshot,
) -> Bool {
snapshot.validate_func_body_calls > 0
}
///|
fn validate_trace_emit_helper_timing(
trace : (String) -> Unit,
ordinal : Int32,
timing : ValidateHelperTimingSnapshot,
) -> Unit {
if !validate_helper_timing_has_nonzero(timing) {
return
}
trace(
"func[\{ordinal}] helper_timing body_ms=\{validate_trace_delta_us_to_ms(timing.validate_func_body_us)} body_calls=\{timing.validate_func_body_calls}",
)
}
///|
fn validate_helper_totals_line() -> String {
"helper_totals body_ms=\{validate_trace_delta_us_to_ms(validate_helper_timing_validate_func_body_us.val)} body_calls=\{validate_helper_timing_validate_func_body_calls.val}"
}
///|
fn validate_trace_emit_totals(trace : (String) -> Unit) -> Unit {
trace(validate_phase_totals_line())
trace(validate_helper_totals_line())
}
///|
fn validate_hotspot_reset() -> Unit {
validate_hotspot_entries.val = []
}
///|
fn validate_hotspot_record(
ordinal : Int32,
body_ticks : UInt64,
locals : Int32,
top_instrs : Int32,
) -> Unit {
let entries = validate_hotspot_entries.val
entries.push({ ordinal, body_ticks, locals, top_instrs, })
if entries.length() <= VALIDATE_HOTSPOT_SUMMARY_LIMIT {
validate_hotspot_entries.val = entries
return
}
let mut min_idx = 0
let mut min_ticks = entries[0].body_ticks
for i = 1; i < entries.length(); i = i + 1 {
if entries[i].body_ticks < min_ticks {
min_idx = i
min_ticks = entries[i].body_ticks
}
}
let trimmed : Array[ValidateHotspotEntry] = []
for i = 0; i < entries.length(); i = i + 1 {
if i != min_idx {
trimmed.push(entries[i])
}
}
validate_hotspot_entries.val = trimmed
}
///|
fn validate_hotspot_summary_line() -> String? {
let entries = validate_hotspot_entries.val
if entries.is_empty() {
return None
}
let ordered = entries.copy()
let mut i = 0
while i < ordered.length() {
let mut j = i + 1
while j < ordered.length() {
if ordered[j].body_ticks > ordered[i].body_ticks {
let tmp = ordered[i]
ordered[i] = ordered[j]
ordered[j] = tmp
}
j += 1
}
i += 1
}
let out = StringBuilder::new()
out.write_string("hotspots ")
let mut idx = 0
while idx < ordered.length() {
if idx > 0 {
out.write_string(" ")
}
let entry = ordered[idx]
out.write_string(
"f\{entry.ordinal}:body=\{entry.body_ticks}:locals=\{entry.locals}:top=\{entry.top_instrs}",
)
idx += 1
}
Some(out.to_string())
}
///|
fn validate_func_trace_locals_count(func : Func) -> Int32 {
match func {
Func(decls, _) => decls.length()
}
}
///|
fn validate_func_trace_top_instrs(func : Func) -> Int32 {
match func {
Func(_, Expr(instrs)) => instrs.length()
}
}
///|
pub trait Validate {
fn validate(Self, Env) -> Result[Unit, String]
}
///|
pub trait ValidateMax {
fn validate_max(Self, Env, UInt64) -> Result[Unit, String]
}
///|
pub impl Validate for NumType with fn validate(_, _) {
Ok(())
}
///|
pub impl Validate for TypeIdx with fn validate(self, env) {
if env.resolve_typeidx_subtype(self) is Some(_) {
Ok(())
} else {
match self {
TypeIdx(raw) =>
Err(
"No type exists for TypeIdx \{raw}; global type count is \{env.global_types.length()}",
)
RecIdx(raw) =>
Err(
"No type exists for RecIdx \{raw}; recursive group depth is \{env.rec_stack.length()}",
)
}
}
}
///|
pub impl Validate for HeapType with fn validate(self, env) {
match self {
AbsHeapTypeHeapType(abs) | SharedAbsHeapTypeHeapType(abs) =>
Validate::validate(abs, env)
HeapType(tidx) => Validate::validate(tidx, env)
DefTypeHeapType(dt) => Validate::validate(dt, env)
}
}
///|
pub impl Validate for DefType with fn validate(self, env) {
let DefType(rt, _) = self
let env2 = env_with_rectype(env, rt)
match self.project() {
Some(st) => Validate::validate(st, env2)
None => Err("Invalid SubType Index")
}
}
///|
pub impl Validate for AbsHeapType with fn validate(_, _) {
Ok(())
}
///|
pub impl Validate for ValType with fn validate(self, env) {
match self {
RefTypeValType(rt) => Validate::validate(rt, env)
VecTypeValType => Ok(())
NumTypeValType(nt) => Validate::validate(nt, env)
BotValType => Ok(())
}
}
///|
pub impl[T : Validate] Validate for Array[T] with fn validate(self, env) {
for v in self {
if Validate::validate(v, env) is Err(t) {
return Err(t)
}
}
Ok(())
}
///|
pub impl Validate for BlockType with fn validate(self, env) {
match self {
ValTypeBlockType(vt) => Validate::validate(vt, env)
VoidBlockType => Ok(())
TypeIdxBlockType(tidx) =>
match env.resolve_typeidx_subtype(tidx) {
Some(st) =>
match st.get_comptype() {
FuncCompType(_, _) => Ok(())
_ => Err("Expected Func Type for block type")
}
None => Err("Invalid TypeIdx for block type")
}
}
}
///|
pub impl Validate for FuncType with fn validate(self, env) {
let FuncType(t0, t1) = self
match Validate::validate(t0, env) {
Ok(_) => Validate::validate(t1, env)
Err(t) => Err(t)
}
}
///|
pub impl Validate for CompType with fn validate(self, env) {
match self {
StructCompType(fts) => Validate::validate(fts, env)
ArrayCompType(ft) => Validate::validate(ft, env)
FuncCompType(v0, v1) =>
match Validate::validate(v0, env) {
Ok(_) => Validate::validate(v1, env)
Err(t) => Err(t)
}
ContCompType(func_type) =>
match env.resolve_functype(func_type) {
Some(_) => Ok(())
None => Err("continuation type must reference a function type")
}
}
}
///|
pub impl Validate for RefType with fn validate(self, env) {
match self {
HeapTypeRefType(_, exact, ht) =>
if exact {
match ht {
HeapType(_) => Validate::validate(ht, env)
_ => Err("exact refs require a defined heap type")
}
} else {
Validate::validate(ht, env)
}
AbsHeapTypeRefType(abs) => Validate::validate(abs, env)
}
}
///|
pub impl Validate for FieldType with fn validate(self, env) {
let FieldType(st, _) = self
Validate::validate(st, env)
}
///|
pub impl Validate for StorageType with fn validate(self, env) {
match self {
ValTypeStorageType(vt) => Validate::validate(vt, env)
PackTypeStorageType(_) => Ok(())
}
}
///|
fn validate_rectype_base(rt : RecType, env : Env) -> Int32 {
let group = match env.rec_stack.get(env.rec_stack.length() - 1) {
Some(group) => group
None => return env.global_types.length()
}
let already_scoped = match rt {
SingleRecType(st) => group.length() == 1 && physical_equal(group[0], st)
GroupRecType(sts) => physical_equal(group, sts)
}
if !already_scoped {
return env.global_types.length()
}
match suffix_start_index(env.global_types.length(), group.length()) {
Some(base) => base
None => env.global_types.length()
}
}
///|
pub impl Validate for RecType with fn validate(self, env) {
let sts = match self {
SingleRecType(st) => [st]
GroupRecType(sts) => sts
}
let base = validate_rectype_base(self, env)
if validate_rectype_parent_order(sts, base) is Err(error) {
return Err(error)
}
if Validate::validate(sts, env) is Err(t) {
return Err(t)
}
validate_descriptor_metadata_group(sts, env, base)
}
///|
fn descriptor_target_absolute_index(
target : TypeIdx,
base : Int32,
group_len : Int32,
) -> Int32? {
match target {
TypeIdx(idx) => Some(idx.reinterpret_as_int())
RecIdx(idx) => {
let rel = idx.reinterpret_as_int()
group_relative_absolute_index(base, group_len, rel)
}
}
}
///|
fn descriptor_target_lookup_subtype(
target : TypeIdx,
sts : Array[SubType],
env : Env,
base : Int32,
) -> SubType? {
let target_abs = match
descriptor_target_absolute_index(target, base, sts.length()) {
Some(target_abs) => target_abs
None => return None
}
match group_member_relative_index(base, sts.length(), target_abs) {
Some(rel_idx) => return sts.get(rel_idx)
None => ()
}
env.resolve_subtype(target)
}
///|
fn validate_rectype_parent_order(
sts : Array[SubType],
base : Int32,
) -> Result[Unit, String] {
for i in 0.. {
if supers.length() > 1 {
return Err("subtype must have at most one supertype")
}
for super_idx in supers {
let parent = match
descriptor_target_absolute_index(super_idx, base, sts.length()) {
Some(parent) => parent
None => return Err("Invalid subtype")
}
if parent < 0 || parent >= base + i {
return Err("supertype must be previously defined")
}
}
}
_ => ()
}
}
Ok(())
}
///|
fn validate_descriptor_metadata_group(
sts : Array[SubType],
env : Env,
base : Int32,
) -> Result[Unit, String] {
let mut i = 0
while i < sts.length() {
let st = sts[i]
let ct = st.get_comptype()
let meta = st.metadata()
let self_abs = base + i
if (meta.describes is Some(_) || meta.descriptor is Some(_)) &&
!(ct is StructCompType(_)) {
return Err("descriptor/describes clauses may only appear on struct types")
}
match meta.describes {
Some(target) => {
let target_abs = match
descriptor_target_absolute_index(target, base, sts.length()) {
Some(target_abs) => target_abs
None => return Err("describes target must be a valid type")
}
if target_abs >= self_abs {
return Err("describes target must be previously defined")
}
let target_st = match
descriptor_target_lookup_subtype(target, sts, env, base) {
Some(target_st) => target_st
None => return Err("describes target must be a valid type")
}
if !(target_st.get_comptype() is StructCompType(_)) {
return Err("describes target must be a struct type")
}
let target_descriptor_abs = match target_st.descriptor_type() {
Some(target_descriptor) =>
descriptor_target_absolute_index(
target_descriptor,
base,
sts.length(),
)
None => None
}
if target_descriptor_abs != Some(self_abs) {
return Err("descriptor/describes clauses must agree")
}
}
None => ()
}
match meta.descriptor {
Some(target) => {
let target_st = match
descriptor_target_lookup_subtype(target, sts, env, base) {
Some(target_st) => target_st
None => return Err("descriptor target must be a valid type")
}
if !(target_st.get_comptype() is StructCompType(_)) {
return Err("descriptor target must be a struct type")
}
let target_describes_abs = match target_st.describes_type() {
Some(target_describes) =>
descriptor_target_absolute_index(
target_describes,
base,
sts.length(),
)
None => None
}
if target_describes_abs != Some(self_abs) {
return Err("descriptor/describes clauses must agree")
}
}
None => ()
}
match st {
SubType(_, supers, _, _) =>
for super_idx in supers {
let super_st = match env.resolve_subtype(super_idx) {
Some(super_st) => super_st
None => return Err("Invalid subtype")
}
let super_descriptor = super_st.descriptor_type()
let super_describes = super_st.describes_type()
match meta.descriptor {
Some(desc_idx) =>
match super_descriptor {
Some(super_desc_idx) =>
if !Match::matches(
HeapType::new(desc_idx),
HeapType::new(super_desc_idx),
env,
) {
return Err(
"descriptor clause is incompatible with supertype",
)
}
None => ()
}
None =>
if super_descriptor is Some(_) {
return Err(
"descriptor-less subtype cannot extend descriptor-bearing supertype",
)
}
}
match meta.describes {
Some(describes_idx) =>
match super_describes {
Some(super_describes_idx) =>
if !Match::matches(
HeapType::new(describes_idx),
HeapType::new(super_describes_idx),
env,
) {
return Err(
"describes clause is incompatible with supertype",
)
}
None =>
return Err(
"describing subtype must extend a describing supertype",
)
}
None =>
if super_describes is Some(_) {
return Err(
"non-describing subtype cannot extend describing supertype",
)
}
}
}
_ => ()
}
i += 1
}
Ok(())
}
///|
fn validate_shared_val_type(vt : ValType, env : Env) -> Bool {
match vt {
RefTypeValType(rt) =>
heaptype_validation_domain(rt.get_heap_type(), env) >= 8
_ => true
}
}
///|
fn validate_shared_comptype(ct : CompType, env : Env) -> Result[Unit, String] {
match ct {
StructCompType(fields) =>
for field in fields {
if !validate_shared_val_type(field.unpack(), env) {
return Err("shared composite type must contain shared types")
}
}
ArrayCompType(field) =>
if !validate_shared_val_type(field.unpack(), env) {
return Err("shared composite type must contain shared types")
}
FuncCompType(params, results) => {
for vt in params {
if !validate_shared_val_type(vt, env) {
return Err("shared composite type must contain shared types")
}
}
for vt in results {
if !validate_shared_val_type(vt, env) {
return Err("shared composite type must contain shared types")
}
}
}
ContCompType(func_type) =>
match env.resolve_subtype(func_type) {
Some(st) if st.metadata().is_shared() => ()
_ => return Err("shared continuation must use a shared function type")
}
}
Ok(())
}
///|
pub impl Validate for SubType with fn validate(self, env) {
let ct = self.get_comptype()
if Validate::validate(ct, env) is Err(error) {
return Err(error)
}
let shared = self.metadata().is_shared()
if shared && validate_shared_comptype(ct, env) is Err(error) {
return Err(error)
}
match self {
SubType(_, supers, _, _) => {
if supers.length() > 1 {
return Err("subtype must have at most one supertype")
}
for super_idx in supers {
let parent = match env.resolve_subtype(super_idx) {
Some(parent) => parent
None => return Err("Invalid subtype")
}
if parent is CompTypeSubType(_, _) || parent is SubType(true, _, _, _) {
return Err("Invalid subtype")
}
if shared != parent.metadata().is_shared() ||
!Match::matches(ct, parent.get_comptype(), env) {
return Err("Invalid subtype")
}
}
Ok(())
}
CompTypeSubType(_, _) => Ok(())
}
}
///|
pub impl ValidateMax for Limits with fn validate_max(self, _, k) {
match self {
I32Limits(n, m) => {
let n = n.to_uint64()
if n <= k &&
(
m is None ||
(m is Some(m) && n <= m.to_uint64() && m.to_uint64() <= k)
) {
Ok(())
} else {
Err("Invalid range for limits")
}
}
I64Limits(n, m) =>
if n <= k && (m is None || (m is Some(m) && n <= m && m <= k)) {
Ok(())
} else {
Err("Invalid range for limits")
}
}
}
///|
pub impl Validate for LabelIdx with fn validate(self, env) {
let LabelIdx(i) = self
if i < env.labels.length().reinterpret_as_uint() {
Ok(())
} else {
Err("Invalid LabelIdx")
}
}
///|
pub impl Validate for TagIdx with fn validate(self, env) {
let TagIdx(i) = self
if i < env.tags.length().reinterpret_as_uint() {
Ok(())
} else {
Err("Invalid TagIdx")
}
}
///|
pub impl Validate for TagType with fn validate(self, env) {
let TagType(tidx) = self
if Validate::validate(tidx, env) is Err(t) {
return Err(t)
}
match env.resolve_comptype(tidx) {
Some(FuncCompType(params, results)) =>
// Suspension tags carry return values. Exception-only instructions
// enforce their empty-result restriction at the use site.
match Validate::validate(params, env) {
Err(err) => Err(err)
Ok(_) => Validate::validate(results, env)
}
_ => Err("TagType not found or not a function type")
}
}
///|
pub impl Validate for TagSec with fn validate(self, env) {
let TagSec(t) = self
Validate::validate(t, env)
}
///|
pub impl Validate for Catch with fn validate(self, env) {
match self {
Catch(t, l) =>
match Validate::validate(t, env) {
Ok(_) =>
match env.get_catch_label_types(l) {
Some(_) => Ok(())
None => Err("Invalid LabelIdx")
}
Err(t) => Err(t)
}
CatchRef(t, l) =>
match Validate::validate(t, env) {
Ok(_) =>
match env.get_catch_label_types(l) {
Some(_) => Ok(())
None => Err("Invalid LabelIdx")
}
Err(t) => Err(t)
}
CatchAll(l) =>
match env.get_catch_label_types(l) {
Some(_) => Ok(())
None => Err("Invalid LabelIdx")
}
CatchAllRef(l) =>
match env.get_catch_label_types(l) {
Some(_) => Ok(())
None => Err("Invalid LabelIdx")
}
}
}
///|
pub impl Validate for GlobalType with fn validate(self, env) {
let GlobalType(vt, _) = self
Validate::validate(vt, env)
}
///|
pub impl Validate for MemType with fn validate(self, env) {
let MemType(lim, shared) = self
if shared {
match lim {
I32Limits(_, Some(_)) | I64Limits(_, Some(_)) => ()
_ => return Err("shared memories require a max")
}
}
let max_pages = match lim {
I32Limits(_, _) => 65536UL
I64Limits(_, _) => 281474976710656UL
}
ValidateMax::validate_max(lim, env, max_pages)
}
///|
pub impl Validate for TableType with fn validate(self, env) {
let TableType(rt, lim) = self
match Validate::validate(rt, env) {
Ok(_) => {
let max_elements = match lim {
I32Limits(_, _) => 4294967295UL
I64Limits(_, _) => 18446744073709551615UL
}
ValidateMax::validate_max(lim, env, max_elements)
}
Err(t) => Err(t)
}
}
///|
pub impl Validate for ExternType with fn validate(self, env) {
match self {
TagExternType(tt) => Validate::validate(tt, env)
GlobalExternType(gt) => Validate::validate(gt, env)
MemExternType(mt) => Validate::validate(mt, env)
TableExternType(tt) => Validate::validate(tt, env)
FuncExternType(tidx) =>
match env.resolve_functype(tidx) {
Some(_) => Ok(())
None => Err("Invalid ExternType: Func extern type not found")
}
}
}
///|
fn validate_const_instr(env : Env, ins : Instruction) -> Result[Unit, String] {
match ins {
I32Const(_) => Ok(())
I64Const(_) => Ok(())
F32Const(_) => Ok(())
F64Const(_) => Ok(())
V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => Ok(())
RefNull(rt) => Validate::validate(rt, env)
RefFunc(_) => Ok(())
WaitqueueNew => Ok(())
StringConst(_) => Ok(())
StructNew(type_idx)
| StructNewDefault(type_idx)
| StructNewDesc(type_idx)
| StructNewDefaultDesc(type_idx) =>
match env.resolve_struct_fields(type_idx) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
ArrayNew(type_idx)
| ArrayNewDefault(type_idx)
| ArrayNewFixed(type_idx, _) =>
match env.resolve_array_field(type_idx) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
GlobalGet(idx) =>
match env.get_global_type(idx) {
None => Err("constant expression references an unknown global")
// This package uses extended-const global.get semantics, but only for
// immutable globals (imported or previously-defined in module order).
Some(GlobalType(_, is_mut)) =>
if is_mut {
Err("constant expression global.get requires immutable global")
} else {
Ok(())
}
}
// WebAssembly 3.0 extended constant expressions add only integer
// add/sub/mul to the core numeric constants. Reference and GC
// constructors/conversions remain explicitly enumerated below; every
// other instruction is rejected by default.
I32Add
| I32Sub
| I32Mul
| I64Add
| I64Sub
| I64Mul
| AnyConvertExtern
| ExternConvertAny
| RefI31
| RefI31Shared => Ok(())
_ => Err("initializer expression is not constant")
}
}
///|
fn validate_const_expr(
env : Env,
expr : Expr,
want : ValType,
) -> Result[Unit, String] {
let Expr(instrs) = expr
// Explicit constant-expression gate: do not let general expression typing
// admit instructions that are invalid in const contexts.
for ins in instrs {
if validate_const_instr(env, ins) is Err(e) {
return Err(e)
}
}
// typecheck it under an empty stack/locals/labels context
// (but with access to env globals, funcs, etc)
let env_tc = Env::{
..env,
locals: Locals::empty(),
labels: LabelStack::new(),
return_type: None,
}
let st0 = TcState::{
env: env_tc,
stack: [],
reachable: true,
polymorphic: false,
escape: NoTcEscape,
reachable_escape_depths: [],
initialized_locals: [],
}
let st1 = match Typecheck::typecheck(expr, st0) {
Ok(s) => s
Err(e) => return Err(e)
}
if !st1.reachable {
return Err("const expr is unreachable")
}
if st1.stack.length() != 1 {
return Err("const expr must leave exactly one value on the stack")
}
let actual = st1.stack[0]
if Match::matches(actual, want, env_tc) {
Ok(())
} else {
Err("const expr result type mismatch")
}
}
///|
fn validate_global(env : Env, g : Global) -> Result[GlobalType, String] {
let Global(gt, init) = g
if Validate::validate(gt, env) is Err(t) {
return Err(t) // checks vt validity
}
let GlobalType(vt, _mut) = gt
match validate_const_expr(env, init, vt) {
Ok(_) => Ok(gt)
Err(t) => Err(t)
}
}
///|
pub fn validate_globalsec(
globalsec : GlobalSec?,
env0 : Env,
) -> Result[Env, String] {
match globalsec {
None => Ok(env0)
Some(GlobalSec(gs)) => {
let mut env = env0
let out = env.globals.copy()
for g in gs {
// Validate under env that includes *previous* globals only
let gt = match validate_global(env, g) {
Ok(gt) => gt
Err(t) => return Err(t)
}
// Extend global index space
out.push(gt)
env = env.with_globals(out)
}
Ok(env)
}
}
}
///|
pub fn validate_tagsec(tagsec : TagSec?, env0 : Env) -> Result[Env, String] {
match tagsec {
None => Ok(env0)
Some(TagSec(tags)) => {
let mut env = env0
let out = env.tags.copy()
for t in tags {
// validate tag type under current env
if Validate::validate(t, env) is Err(t) {
return Err(t)
}
// extend tag index space incrementally
out.push(t)
env = Env::{
global_types: env.global_types,
rec_stack: env.rec_stack,
funcs: env.funcs,
func_type_idxs: env.func_type_idxs,
tables: env.tables,
mems: env.mems,
globals: env.globals,
elems: env.elems,
datas: env.datas,
tags: out,
locals: env.locals,
labels: env.labels,
return_type: env.return_type,
legacy_catch_depth: env.legacy_catch_depth,
infer_exact_references: env.infer_exact_references,
}
}
Ok(env)
}
}
}
///|
pub fn validate_memsec(memsec : MemSec?, env0 : Env) -> Result[Env, String] {
match memsec {
None => Ok(env0)
Some(MemSec(ms)) => {
let mut env = env0
let mems = env.mems.copy()
for mt in ms {
match Validate::validate(mt, env) {
Ok(_) => ()
Err(e) => return Err(e)
}
mems.push(mt)
env = env.with_mems(mems)
}
Ok(env)
}
}
}
///|
fn validate_table(env : Env, t : Table) -> Result[TableType, String] {
let Table(tt, init_opt) = t
match Validate::validate(tt, env) {
Ok(_) => ()
Err(e) => return Err(e)
}
let TableType(rt, _lim) = tt
match init_opt {
None => Ok(tt)
Some(init) => {
// init expr must be const and produce value type rt
let want = ValType::ref_type(rt)
match validate_const_expr(env, init, want) {
Ok(_) => Ok(tt)
Err(e) => Err(e)
}
}
}
}
///|
pub fn validate_tablesec(
tablesec : TableSec?,
env0 : Env,
) -> Result[Env, String] {
match tablesec {
None => Ok(env0)
Some(TableSec(ts)) => {
let mut env = env0
let tables = env.tables.copy()
for t in ts {
let tt = match validate_table(env, t) {
Ok(x) => x
Err(e) => return Err(e)
}
tables.push(tt)
env = env.with_tables(tables)
}
Ok(env)
}
}
}
///|
fn validate_locals_decls(env : Env, decls : Locals) -> Result[Unit, String] {
for d in decls.runs() {
let { count, vt, } = d
if Validate::validate(vt, env) is Err(e) {
return Err(e)
}
let local_count = count.reinterpret_as_int()
if local_count < 0 {
return Err("locals: negative count")
}
}
Ok(())
}
///|
fn validate_append_all_locals_from_decls(
params : Array[ValType],
decls : Locals,
) -> Locals {
let all = Locals::from_types(params)
all.append(decls)
all
}
///|
fn validate_initial_local_initialized(
params : Array[ValType],
decls : Locals,
) -> Array[Bool] {
let mut total = params.length()
for run in decls.runs() {
let count = run.count.reinterpret_as_int()
if count > 0 {
total += count
}
}
let initialized = Array::make(total, true)
let mut offset = params.length()
for run in decls.runs() {
let count = run.count.reinterpret_as_int()
if count > 0 {
if !has_default(run.vt) {
initialized.fill(false, start=offset, end=offset + count)
}
offset += count
}
}
initialized
}
///|
fn validate_end_stack_shape_suffix(
st : TcState,
results : Array[ValType],
) -> String {
"expected stack=\{results} actual stack=\{st.stack}"
}
///|
fn validate_end_stack_for_function_body(
st : TcState,
results : Array[ValType],
subject : String,
) -> Result[Unit, String] {
match check_pop_types_from_top(st, results, results.length()) {
Err("stack underflow") =>
Err(
"\{subject} result stack underflow: \{validate_end_stack_shape_suffix(st, results)}",
)
Err("type mismatch") =>
Err(
"\{subject} result type mismatch: \{validate_end_stack_shape_suffix(st, results)}",
)
Err(e) => Err(e)
Ok(top_after_results) =>
if top_after_results != -1 {
Err(
"\{subject} leaves extra values on stack: \{validate_end_stack_shape_suffix(st, results)}",
)
} else {
Ok(())
}
}
}
///|
/// Validate one body against a resolved signature and unchanged module
/// environment. Each invocation owns its local stack, labels and initialization
/// state, so callers may reuse the module environment across sibling bodies.
pub fn validate_func_body_against_functype(
env : Env,
ft : FuncType,
f : Func,
) -> Result[Unit, String] {
let FuncType(params, results) = ft
match f {
Func(local_decls, body) => {
// Validate local declarations
if validate_locals_decls(env, local_decls) is Err(e) {
return Err(e)
}
// Extend context:
// - locals
// - labels appended by results (function label)
// - return appended by results
let env1 = env
.with_locals(validate_append_all_locals_from_decls(params, local_decls))
.with_label(results)
.with_return_type(Some(results))
let st0 = TcState::{
env: env1,
stack: [],
reachable: true,
polymorphic: false,
escape: NoTcEscape,
reachable_escape_depths: [],
initialized_locals: validate_initial_local_initialized(
params, local_decls,
),
}
let st1 = match Typecheck::typecheck(body, st0) {
Ok(s) => s
Err(e) => return Err(e)
}
match
validate_end_stack_for_function_body(st1, results, "function body") {
Err(e) => return Err("\{e}; body=\{body}")
Ok(_) => ()
}
Ok(())
}
}
}
///|
fn validate_codesec_diag(
codesec : CodeSec?,
funcsec : FuncSec?,
env : Env,
trace~ : (String) -> Unit = validate_trace_noop,
trace_all_funcs~ : Bool = false,
helper_timing_enabled~ : Bool = false,
) -> Result[Unit, ValidationDiagnostic] {
match (codesec, funcsec) {
(None, None) => Ok(())
(Some(CodeSec(bodies)), None) =>
if bodies.length() == 0 {
Ok(())
} else {
Err(
ValidationDiagnostic::new(
ValidationIssue::CodeSection("codesec present without funcsec"),
),
)
}
(None, Some(FuncSec(ts))) =>
if ts.length() == 0 {
Ok(())
} else {
Err(
ValidationDiagnostic::new(
ValidationIssue::CodeSection("funcsec present without codesec"),
),
)
}
(Some(CodeSec(bodies)), Some(FuncSec(type_idxs))) => {
if bodies.length() != type_idxs.length() {
return Err(
ValidationDiagnostic::new(
ValidationIssue::CodeSection(
"codesec length does not match funcsec length",
),
),
)
}
let n_defined = bodies.length()
let n_total = env.funcs.length()
let n_imported = match suffix_start_index(n_total, n_defined) {
Some(n_imported) => n_imported
None =>
return Err(
ValidationDiagnostic::new(
ValidationIssue::CodeSection(
"internal: negative imported func count",
),
),
)
}
for i in 0.. x
None => {
if log_func {
trace(
"func[\{ordinal}] done elapsed_ms=\{validate_trace_elapsed_ms(func_start_ms)} valid=false reason=missing_functype",
)
}
return Err(
ValidationDiagnostic::new(
ValidationIssue::CodeSection(
"internal: missing functype for code body",
),
func_idx=Some(func_idx),
),
)
}
}
let body_start_us = if helper_timing_enabled {
validate_trace_now_us()
} else {
0UL
}
let body_result = validate_func_body_against_functype(env, ft, func)
validate_helper_timing_commit(
helper_timing_enabled, validate_helper_timing_validate_func_body_us, validate_helper_timing_validate_func_body_calls,
body_start_us,
)
if log_func {
let helper_timing_delta = validate_helper_timing_diff(
validate_helper_timing_snapshot(),
helper_timing_start,
)
validate_hotspot_record(
ordinal,
helper_timing_delta.validate_func_body_us,
func_locals,
func_top_instrs,
)
validate_trace_emit_helper_timing(trace, ordinal, helper_timing_delta)
}
if body_result is Err(e) {
if log_func {
trace(
"func[\{ordinal}] done elapsed_ms=\{validate_trace_elapsed_ms(func_start_ms)} valid=false",
)
}
return Err(
ValidationDiagnostic::new(
ValidationIssue::FunctionBody(e),
func_idx=Some(func_idx),
),
)
}
if log_func {
trace(
"func[\{ordinal}] done elapsed_ms=\{validate_trace_elapsed_ms(func_start_ms)} valid=true",
)
}
}
if trace_all_funcs {
match validate_hotspot_summary_line() {
Some(summary) => trace(summary)
None => ()
}
}
Ok(())
}
}
}
///|
pub fn validate_codesec(
codesec : CodeSec?,
funcsec : FuncSec?,
env : Env,
) -> Result[Unit, String] {
match validate_codesec_diag(codesec, funcsec, env) {
Ok(_) => Ok(())
Err(diag) => Err(validation_issue_message(diag.issue))
}
}
///|
pub fn validate_funcsec(funcsec : FuncSec?, env0 : Env) -> Result[Env, String] {
match funcsec {
None => Ok(env0)
Some(FuncSec(type_idxs)) => {
let mut env = env0
let funcs = env.funcs.copy()
let func_type_idxs = env.func_type_idxs.copy()
for tidx in type_idxs {
// Each TypeIdx must resolve to a function type
let ft = match env.resolve_functype(tidx) {
Some(x) => x
None => return Err("funcsec: typeidx does not resolve to functype")
}
funcs.push(ft)
func_type_idxs.push(Some(tidx))
env = Env::{ ..env, funcs, func_type_idxs, }
}
Ok(env)
}
}
}
///|
fn validate_env_before_codesec(m : Module) -> Result[Env, String] {
let mut env = Env::new()
env = match validate_typesec(m.type_sec, env) {
Ok(env) => env
Err(err) => return Err(err)
}
env = match validate_importsec(m.import_sec, env) {
Ok(env) => env
Err(err) => return Err(err)
}
env = match validate_funcsec(m.func_sec, env) {
Ok(env) => env
Err(err) => return Err(err)
}
env = match validate_tablesec(m.table_sec, env) {
Ok(env) => env
Err(err) => return Err(err)
}
env = match validate_memsec(m.mem_sec, env) {
Ok(env) => env
Err(err) => return Err(err)
}
env = match validate_tagsec(m.tag_sec, env) {
Ok(env) => env
Err(err) => return Err(err)
}
env = match validate_globalsec(m.global_sec, env) {
Ok(env) => env
Err(err) => return Err(err)
}
env = match validate_elemsec(m.elem_sec, env) {
Ok(env) => env
Err(err) => return Err(err)
}
env = match validate_datasec(m.data_sec, env) {
Ok(env) => env
Err(err) => return Err(err)
}
Ok(env)
}
///|
pub fn validate_defined_func_against_module(
m : Module,
func_idx : FuncIdx,
f : Func,
) -> Result[Unit, String] {
let env = match validate_env_before_codesec(m) {
Ok(env) => env
Err(err) => return Err(err)
}
let ft = match env.get_functype_by_funcidx(func_idx) {
Some(ft) => ft
None => return Err("missing functype for defined function")
}
validate_func_body_against_functype(env, ft, f)
}
///|
pub fn validate_defined_funcs_against_module(
m : Module,
funcs : Array[(FuncIdx, Func)],
) -> Result[Array[(FuncIdx, String)], ValidationError] {
let env = match validate_env_before_codesec(m) {
Ok(env) => env
Err(err) =>
return Err(
ValidationError::Validation(
ValidationDiagnostic::new(ValidationIssue::CodeSection(err)),
),
)
}
let errors : Array[(FuncIdx, String)] = []
for item in funcs {
let (func_idx, f) = item
let ft = match env.get_functype_by_funcidx(func_idx) {
Some(ft) => ft
None => {
errors.push((func_idx, "missing functype for defined function"))
continue
}
}
match validate_func_body_against_functype(env, ft, f) {
Ok(_) => ()
Err(err) => errors.push((func_idx, err))
}
}
Ok(errors)
}
///|
pub impl Validate for DataMode with fn validate(self, env) {
match self {
Passive => Ok(())
Active(memidx, offset_expr) => {
// memory must exist
let MemType(lim, _) = match env.get_memtype(memidx) {
None => return Err("data.active: invalid memory index")
Some(mt) => mt
}
// address type at depends on limits (i32 or i64)
let at = lim.addr_valtype()
// expr : at const
validate_const_expr(env, offset_expr, at)
}
}
}
///|
pub impl Validate for Data with fn validate(self, env) {
let Data(mode, _) = self
Validate::validate(mode, env)
}
///|
pub fn validate_datasec(datasec : DataSec?, env0 : Env) -> Result[Env, String] {
match datasec {
None => Ok(env0)
Some(DataSec(ds)) => {
let mut env = env0
let datas = env.datas.copy()
for d in ds {
// validate under env that includes previous data segments
if Validate::validate(d, env) is Err(e) {
return Err(e)
}
datas.push(d)
env = Env::{ ..env, datas, }
}
Ok(env)
}
}
}
///|
fn validate_elem_mode(
env : Env,
mode : ElemMode,
rt : RefType,
) -> Result[Unit, String] {
match mode {
Passive => Ok(())
Declarative => Ok(())
Active(tidx, offset_expr) => {
// table must exist
let TableType(rt_table, lim) = match env.get_table_type(tidx) {
None => return Err("elem.active: invalid table index")
Some(tt) => tt
}
// require rt ≤ rt_table
// If your Match::matches works on RefType, use that.
// Otherwise use ValType wrapper.
let ok = Match::matches(
ValType::ref_type(rt),
ValType::ref_type(rt_table),
env,
)
if !ok {
return Err("elem.active: elem type does not match table element type")
}
// offset expr : at const
let at = lim.addr_valtype()
validate_const_expr(env, offset_expr, at)
}
}
}
///|
pub impl Validate for ElemKind with fn validate(self, env) {
match self {
// elemtype is funcref; initializers are function indices
FuncsElemKind(fs) => {
for f in fs {
if !env.has_func(f) {
return Err("elem.funcs: invalid function index")
}
}
Ok(())
}
// legacy element expressions: elemtype is funcref; each expr must be const funcref
FuncExprsElemKind(exprs) => {
let want = ValType::funcref()
for e in exprs {
if validate_const_expr(env, e, want) is Err(t) {
return Err(t)
}
}
Ok(())
}
// typed element expressions: elemtype is provided; each expr must be const of that type
TypedExprsElemKind(rt, exprs) => {
if Validate::validate(rt, env) is Err(t) {
return Err(t)
}
let want = ValType::ref_type(rt)
for e in exprs {
if validate_const_expr(env, e, want) is Err(t) {
return Err(t)
}
}
Ok(())
}
}
}
///|
pub impl Validate for Elem with fn validate(self, env) {
let Elem(mode, kind) = self
// kind validity (incl. elemtype and init exprs)
if Validate::validate(kind, env) is Err(t) {
return Err(t)
}
// derive elemtype from kind and validate it
let rt = Elem::reftype(self)
if Validate::validate(rt, env) is Err(t) {
return Err(t)
}
// mode validity depends on elemtype
validate_elem_mode(env, mode, rt)
}
///|
pub fn validate_elemsec(elemsec : ElemSec?, env0 : Env) -> Result[Env, String] {
match elemsec {
None => Ok(env0)
Some(ElemSec(es)) => {
let mut env = env0
let elems = env.elems.copy()
for e in es {
if Validate::validate(e, env) is Err(t) {
return Err(t)
}
elems.push(e)
env = env.with_elems(elems)
}
Ok(env)
}
}
}
///|
pub impl Validate for Import with fn validate(self, env) {
let Import(_, _, xt) = self
Validate::validate(xt, env)
}
///|
pub fn validate_importsec(
importsec : ImportSec?,
env0 : Env,
) -> Result[Env, String] {
match importsec {
None => Ok(env0)
Some(ImportSec(imps)) => {
let mut env = env0
let funcs = env.funcs.copy()
let func_type_idxs = env.func_type_idxs.copy()
let tables = env.tables.copy()
let mems = env.mems.copy()
let globals = env.globals.copy()
let tags = env.tags.copy()
for imp in imps {
if Validate::validate(imp, env) is Err(e) {
return Err(e)
}
let Import(_, _, et) = imp
match et {
FuncExternType(tidx) => {
let ft = match env.resolve_functype(tidx) {
Some(x) => x
None => return Err("import func: type index is not a functype")
}
funcs.push(ft)
func_type_idxs.push(Some(tidx))
}
TableExternType(tt) => tables.push(tt)
MemExternType(mt) => mems.push(mt)
GlobalExternType(gt) => globals.push(gt)
TagExternType(tt) => tags.push(tt)
}
env = Env::{
..env,
funcs,
func_type_idxs,
tables,
mems,
globals,
tags,
}
}
Ok(env)
}
}
}
///|
pub fn validate_startsec(
startsec : StartSec?,
env : Env,
) -> Result[Unit, String] {
match startsec {
None => Ok(())
Some(StartSec(fidx)) => {
let FuncType(params, results) = match env.get_functype_by_funcidx(fidx) {
None => return Err("start: invalid function index")
Some(ft) => ft
}
if params.length() != 0 {
return Err("start function must have empty parameter list")
}
if results.length() != 0 {
return Err("start function must have empty result list")
}
Ok(())
}
}
}
///|
pub impl Validate for FuncIdx with fn validate(self, env) {
if env.has_func(self) {
Ok(())
} else {
Err("invalid func index")
}
}
///|
pub impl Validate for TableIdx with fn validate(self, env) {
match env.get_table_type(self) {
Some(_) => Ok(())
None => Err("invalid table index")
}
}
///|
pub impl Validate for MemIdx with fn validate(self, env) {
let MemIdx(i) = self
match env.get_memtype(MemIdx::new(i)) {
Some(_) => Ok(())
None => Err("invalid mem index")
}
}
///|
pub impl Validate for GlobalIdx with fn validate(self, env) {
let GlobalIdx(i) = self
match env.get_global_type(GlobalIdx::new(i)) {
Some(_) => Ok(())
None => Err("invalid global index")
}
}
///|
pub impl Validate for ExternIdx with fn validate(self, env) {
match self {
FuncExternIdx(i) => Validate::validate(i, env)
TableExternIdx(i) => Validate::validate(i, env)
MemExternIdx(i) => Validate::validate(i, env)
GlobalExternIdx(i) => Validate::validate(i, env)
TagExternIdx(i) => Validate::validate(i, env)
}
}
///|
pub impl Validate for Export with fn validate(self, env) {
let Export(_, exi) = self
Validate::validate(exi, env)
}
///|
pub fn validate_exportsec(
exportsec : ExportSec?,
env : Env,
) -> Result[Unit, String] {
match exportsec {
None => Ok(())
Some(ExportSec(es)) => Validate::validate(es, env)
}
}
///|
/// Validates the type section incrementally and extends env.global_types.
pub fn validate_typesec(typesec : TypeSec?, env0 : Env) -> Result[Env, String] {
match typesec {
None => Ok(env0)
Some(TypeSec(rts)) => {
if rts.is_empty() {
return Ok(env0)
}
// These two rows are private to this synchronous validation invocation.
// No intermediate Env escapes: group checks return only Unit or String.
// Copy the caller's prefix once, then expose exactly one new group at a
// time. Failure drops the private rows without publishing a partial env.
let types = env0.global_types.copy()
let scopes = env0.rec_stack.copy()
let env = Env::{ ..env0, global_types: types, rec_stack: scopes, }
for rt in rts {
let base = types.length()
match rt {
SingleRecType(st) => {
scopes.push([st])
types.push(st)
}
GroupRecType(sts) => {
scopes.push(sts)
types.append(sts)
}
}
// Match env_with_rectype: prior types are normalized and the current
// raw group is visible through both absolute and recursive indices.
if Validate::validate(rt, env) is Err(error) {
return Err(error)
}
// Publish normalized members in place only after the complete group
// validates. Keep original recursive scopes, just as the immutable
// append_rectype_types_with_scope boundary does for other callers.
match normalize_rectype_for_global_env(rt, base) {
SingleRecType(st) => types[base] = st
GroupRecType(sts) =>
for idx in 0.. TypeIdx {
match idx {
TypeIdx(_) => idx
RecIdx(rel_u) =>
match
group_relative_absolute_index(
base,
group_len,
rel_u.reinterpret_as_int(),
) {
Some(abs) => TypeIdx::new(abs.reinterpret_as_uint())
None => idx
}
}
}
///|
fn normalize_subtype_for_global_env(
st : SubType,
base : Int32,
group_len : Int32,
) -> SubType {
let metadata = st.metadata()
let normalized_metadata = TypeMetadata::new(
describes=metadata.describes.map(fn(idx) {
normalize_rectype_typeidx_for_global_env(idx, base, group_len)
}),
descriptor=metadata.descriptor.map(fn(idx) {
normalize_rectype_typeidx_for_global_env(idx, base, group_len)
}),
shared=metadata.shared,
)
let normalized_supers = st
.super_types()
.map(fn(idx) {
normalize_rectype_typeidx_for_global_env(idx, base, group_len)
})
match st {
SubType(is_final, _, _, comp) =>
SubType::new(
is_final,
normalized_supers,
comp,
metadata=normalized_metadata,
)
CompTypeSubType(_, comp) =>
SubType::comp_type(comp, metadata=normalized_metadata)
}
}
///|
fn normalize_rectype_for_global_env(rt : RecType, base : Int32) -> RecType {
match rt {
SingleRecType(st) =>
RecType::new(normalize_subtype_for_global_env(st, base, 1))
GroupRecType(sts) => {
let normalized : Array[SubType] = []
let group_len = sts.length()
for st in sts {
normalized.push(normalize_subtype_for_global_env(st, base, group_len))
}
RecType::group(normalized)
}
}
}
///|
pub fn validate_datacnt(
datacnt_opt : DataCntSec?,
datasec_opt : DataSec?,
) -> Result[Unit, String] {
match datacnt_opt {
None => Ok(())
Some(DataCntSec(U32(n))) => {
let actual : UInt32 = match datasec_opt {
None =>
if n == 0U {
0U
} else {
return Err("datacount section present but no data section")
}
Some(DataSec(ds)) => ds.length().reinterpret_as_uint()
}
if n == actual {
Ok(())
} else {
Err("datacount does not match number of data segments")
}
}
}
}
///|
fn expr_uses_bulk_memory_data_count(expr : Expr) -> Bool {
let Expr(instrs) = expr
for ins in instrs {
if instr_uses_bulk_memory_data_count(ins) {
return true
}
}
false
}
///|
fn instr_uses_bulk_memory_data_count(ins : Instruction) -> Bool {
match ins {
MemoryInit(_, _) | DataDrop(_) => true
Block(_, Expr(body)) | Loop(_, Expr(body)) => {
for inner in body {
if instr_uses_bulk_memory_data_count(inner) {
return true
}
}
false
}
If(_, then_body, else_body) => {
for inner in then_body {
if instr_uses_bulk_memory_data_count(inner) {
return true
}
}
match else_body {
Some(branch) =>
for inner in branch {
if instr_uses_bulk_memory_data_count(inner) {
return true
}
}
None => ()
}
false
}
Try(_, Expr(body), catches, _) => {
for inner in body {
if instr_uses_bulk_memory_data_count(inner) {
return true
}
}
for catch_ in catches {
let Expr(catch_body) = match catch_ {
LegacyCatch(_, catch_body) | LegacyCatchAll(catch_body) => catch_body
}
for inner in catch_body {
if instr_uses_bulk_memory_data_count(inner) {
return true
}
}
}
false
}
TryTable(_, _, Expr(body)) => {
for inner in body {
if instr_uses_bulk_memory_data_count(inner) {
return true
}
}
false
}
_ => false
}
}
///|
fn func_uses_bulk_memory_data_count(func : Func) -> Bool {
match func {
Func(_, expr) => expr_uses_bulk_memory_data_count(expr)
}
}
///|
fn validate_bulk_memory_data_count_requirement(
codesec_opt : CodeSec?,
datacnt_opt : DataCntSec?,
imported_func_count : Int32,
) -> Result[Unit, ValidationDiagnostic] {
if datacnt_opt is Some(_) {
return Ok(())
}
match codesec_opt {
None => Ok(())
Some(CodeSec(funcs)) => {
for i in 0.. FuncIdx::new(func_abs.reinterpret_as_uint())
None => FuncIdx::new(i.reinterpret_as_uint())
}
return Err(
ValidationDiagnostic::new(
ValidationIssue::FunctionBody("data count section required"),
func_idx=Some(func_idx),
),
)
}
}
Ok(())
}
}
}
///|
/// Validates exports including name uniqueness requirement.
fn validate_exportsec_unique(
exportsec : ExportSec?,
env : Env,
) -> Result[Unit, String] {
match exportsec {
None => Ok(())
Some(ExportSec(es)) => {
// First validate all exports
if Validate::validate(es, env) is Err(t) {
return Err(t)
}
// Check for duplicate export names
let names : Array[Name] = []
for e in es {
let Export(name, _) = e
for existing in names {
if existing == name {
return Err("duplicate export name: " + name.0.to_owned())
}
}
names.push(name)
}
Ok(())
}
}
}
///|
fn validate_name_sec_total_locals(
m : Module,
env : Env,
imported_func_count : Int32,
func_idx : UInt32,
) -> Int32? {
let func_idx_int = func_idx.reinterpret_as_int()
let params_len = match env.get_functype_by_funcidx(FuncIdx::new(func_idx)) {
Some(FuncType(params, _)) => params.length()
None => return None
}
match defined_func_body_index(imported_func_count, func_idx_int) {
None => Some(params_len)
Some(defined_idx) =>
match m.code_sec {
None => None
Some(CodeSec(funcs)) =>
match funcs.get(defined_idx) {
Some(Func(locals, _)) => Some(params_len + locals.length())
None => None
}
}
}
}
///|
fn validate_name_sec_instrs_label_count(instrs : Array[Instruction]) -> Int32 {
let mut total = 0
for instr in instrs {
total += validate_name_sec_instr_label_count(instr)
}
total
}
///|
fn validate_name_sec_instr_label_count(instr : Instruction) -> Int32 {
match instr {
Block(_, body) | Loop(_, body) | TryTable(_, _, body) =>
1 + validate_name_sec_expr_label_count(body)
Try(_, body, catches, _) => {
let mut total = 1 + validate_name_sec_expr_label_count(body)
for catch_ in catches {
let catch_body = match catch_ {
LegacyCatch(_, catch_body) | LegacyCatchAll(catch_body) => catch_body
}
total += validate_name_sec_expr_label_count(catch_body)
}
total
}
If(_, then_instrs, else_instrs) => {
let else_count = match else_instrs {
Some(instrs) => validate_name_sec_instrs_label_count(instrs)
None => 0
}
1 + validate_name_sec_instrs_label_count(then_instrs) + else_count
}
_ => 0
}
}
///|
fn validate_name_sec_expr_label_count(expr : Expr) -> Int32 {
let Expr(instrs) = expr
validate_name_sec_instrs_label_count(instrs)
}
///|
fn validate_name_sec_total_labels(
m : Module,
env : Env,
imported_func_count : Int32,
func_idx : UInt32,
) -> Int32? {
if !env.has_func(FuncIdx::new(func_idx)) {
return None
}
let func_idx_int = func_idx.reinterpret_as_int()
match defined_func_body_index(imported_func_count, func_idx_int) {
None => Some(0)
Some(defined_idx) =>
match m.code_sec {
None => None
Some(CodeSec(funcs)) =>
match funcs.get(defined_idx) {
Some(Func(_, expr)) =>
Some(validate_name_sec_expr_label_count(expr))
None => None
}
}
}
}
///|
/// Check name-section references against the module's type and index spaces.
/// This narrow check does not establish validity of function bodies or other
/// sections; callers must retain their full module-validation boundary.
pub fn validate_module_name_section(m : Module) -> Result[Unit, String] {
if m.name_sec is None {
return validate_name_sec(m, Env::new(), 0)
}
// Name bounds need signatures and resource counts, but no inferred types.
// Disable descriptor detection to avoid an unrelated full body scan.
let env = Env::new().with_exact_reference_inference(false).with_module(m)
let mut imported_func_count = 0
if m.import_sec is Some(ImportSec(imports)) {
for import_ in imports {
if import_ is Import(_, _, FuncExternType(_)) {
imported_func_count += 1
}
}
}
validate_name_sec(m, env, imported_func_count)
}
///|
fn validate_name_sec(
m : Module,
env : Env,
imported_func_count : Int32,
) -> Result[Unit, String] {
for custom in m.custom_secs {
let CustomSec(name, _) = custom
if name == Name::new("name") {
return Err("raw name custom section is unsupported; use Module.name_sec")
}
}
let name_sec = match m.name_sec {
Some(sec) => sec
None => return Ok(())
}
match name_sec.func_names {
Some(NameMap(entries)) =>
for entry in entries {
let NameAssoc(func_idx, _) = entry
match bounded_index(env.funcs.length(), func_idx.reinterpret_as_int()) {
Some(_) => ()
None =>
return Err(
"function name index out of range: func=\{func_idx} total_funcs=\{env.funcs.length()}",
)
}
}
None => ()
}
match name_sec.local_names {
Some(IndirectNameMap(entries)) =>
for entry in entries {
let IndirectNameAssoc(func_idx, NameMap(local_entries)) = entry
let total_locals = match
validate_name_sec_total_locals(m, env, imported_func_count, func_idx) {
Some(total) => total
None =>
return Err(
"local names reference missing function: func=\{func_idx}",
)
}
for local_entry in local_entries {
let NameAssoc(local_idx, _) = local_entry
match bounded_index(total_locals, local_idx.reinterpret_as_int()) {
Some(_) => ()
None =>
return Err(
"local name index out of range: func=\{func_idx} local=\{local_idx} total_locals=\{total_locals}",
)
}
}
}
None => ()
}
match name_sec.label_names {
Some(IndirectNameMap(entries)) =>
for entry in entries {
let IndirectNameAssoc(func_idx, NameMap(label_entries)) = entry
let total_labels = match
validate_name_sec_total_labels(m, env, imported_func_count, func_idx) {
Some(total) => total
None =>
return Err(
"label names reference missing function: func=\{func_idx}",
)
}
for label_entry in label_entries {
let NameAssoc(label_idx, _) = label_entry
match bounded_index(total_labels, label_idx.reinterpret_as_int()) {
Some(_) => ()
None =>
return Err(
"label name index out of range: func=\{func_idx} label=\{label_idx} total_labels=\{total_labels}",
)
}
}
}
None => ()
}
match name_sec.type_names {
Some(NameMap(entries)) =>
for entry in entries {
let NameAssoc(type_idx, _) = entry
match
bounded_index(
env.global_types.length(),
type_idx.reinterpret_as_int(),
) {
Some(_) => ()
None =>
return Err(
"type name index out of range: type=\{type_idx} total_types=\{env.global_types.length()}",
)
}
}
None => ()
}
match name_sec.table_names {
Some(NameMap(entries)) =>
for entry in entries {
let NameAssoc(table_idx, _) = entry
match
bounded_index(env.tables.length(), table_idx.reinterpret_as_int()) {
Some(_) => ()
None =>
return Err(
"table name index out of range: table=\{table_idx} total_tables=\{env.tables.length()}",
)
}
}
None => ()
}
match name_sec.memory_names {
Some(NameMap(entries)) =>
for entry in entries {
let NameAssoc(mem_idx, _) = entry
match bounded_index(env.mems.length(), mem_idx.reinterpret_as_int()) {
Some(_) => ()
None =>
return Err(
"memory name index out of range: memory=\{mem_idx} total_memories=\{env.mems.length()}",
)
}
}
None => ()
}
match name_sec.global_names {
Some(NameMap(entries)) =>
for entry in entries {
let NameAssoc(global_idx, _) = entry
match
bounded_index(env.globals.length(), global_idx.reinterpret_as_int()) {
Some(_) => ()
None =>
return Err(
"global name index out of range: global=\{global_idx} total_globals=\{env.globals.length()}",
)
}
}
None => ()
}
match name_sec.elem_names {
Some(NameMap(entries)) =>
for entry in entries {
let NameAssoc(elem_idx, _) = entry
match bounded_index(env.elems.length(), elem_idx.reinterpret_as_int()) {
Some(_) => ()
None =>
return Err(
"elem name index out of range: elem=\{elem_idx} total_elem=\{env.elems.length()}",
)
}
}
None => ()
}
match name_sec.data_names {
Some(NameMap(entries)) =>
for entry in entries {
let NameAssoc(data_idx, _) = entry
match bounded_index(env.datas.length(), data_idx.reinterpret_as_int()) {
Some(_) => ()
None =>
return Err(
"data name index out of range: data=\{data_idx} total_data=\{env.datas.length()}",
)
}
}
None => ()
}
match name_sec.field_names {
Some(IndirectNameMap(entries)) =>
for entry in entries {
let IndirectNameAssoc(type_idx, NameMap(field_entries)) = entry
match
bounded_index(
env.global_types.length(),
type_idx.reinterpret_as_int(),
) {
Some(_) => ()
None =>
return Err(
"field name type index out of range: type=\{type_idx} total_types=\{env.global_types.length()}",
)
}
let fields = match env.resolve_comptype(TypeIdx::new(type_idx)) {
Some(StructCompType(fields)) => fields
Some(_) =>
return Err("field names require struct type: type=\{type_idx}")
None =>
return Err("field names require struct type: type=\{type_idx}")
}
for field_entry in field_entries {
let NameAssoc(field_idx, _) = field_entry
match bounded_index(fields.length(), field_idx.reinterpret_as_int()) {
Some(_) => ()
None =>
return Err(
"field name index out of range: type=\{type_idx} field=\{field_idx} total_fields=\{fields.length()}",
)
}
}
}
None => ()
}
match name_sec.tag_names {
Some(NameMap(entries)) =>
for entry in entries {
let NameAssoc(tag_idx, _) = entry
match bounded_index(env.tags.length(), tag_idx.reinterpret_as_int()) {
Some(_) => ()
None =>
return Err(
"tag name index out of range: tag=\{tag_idx} total_tags=\{env.tags.length()}",
)
}
}
None => ()
}
Ok(())
}
///|
fn mark_declared_func(declared : @bitset.BitSet, f : FuncIdx) -> Unit {
let FuncIdx(i) = f
match bounded_index(declared.length(), i.reinterpret_as_int()) {
Some(ii) => declared[ii] = true
None => ()
}
}
///|
fn mark_declared_ref_funcs_in_instr(
instr : Instruction,
declared : @bitset.BitSet,
) -> Unit {
match instr {
RefFunc(f) => mark_declared_func(declared, f)
Block(_, Expr(body)) | Loop(_, Expr(body)) =>
for inner in body {
mark_declared_ref_funcs_in_instr(inner, declared)
}
If(_, then_body, else_body) => {
for inner in then_body {
mark_declared_ref_funcs_in_instr(inner, declared)
}
match else_body {
Some(branch) =>
for inner in branch {
mark_declared_ref_funcs_in_instr(inner, declared)
}
None => ()
}
}
Try(_, Expr(body), catches, _) => {
for inner in body {
mark_declared_ref_funcs_in_instr(inner, declared)
}
for catch_ in catches {
let Expr(catch_body) = match catch_ {
LegacyCatch(_, catch_body) | LegacyCatchAll(catch_body) => catch_body
}
for inner in catch_body {
mark_declared_ref_funcs_in_instr(inner, declared)
}
}
}
TryTable(_, _, Expr(body)) =>
for inner in body {
mark_declared_ref_funcs_in_instr(inner, declared)
}
_ => ()
}
}
///|
fn mark_declared_ref_funcs_in_expr(
expr : Expr,
declared : @bitset.BitSet,
) -> Unit {
let Expr(instrs) = expr
for instr in instrs {
mark_declared_ref_funcs_in_instr(instr, declared)
}
}
///|
fn collect_declared_funcs_bitmap(mod : Module, env : Env) -> @bitset.BitSet {
let declared = @bitset.BitSet::new(env.funcs.length())
// Declaration sources are module sites outside function bodies, excluding
// start. This includes exports, const-expression ref.funcs in globals/tables
// and elem expressions, plus elem segments with raw function indices.
match mod.export_sec {
Some(ExportSec(exports)) =>
for exp in exports {
match exp {
Export(_, FuncExternIdx(f)) => mark_declared_func(declared, f)
_ => ()
}
}
None => ()
}
match mod.global_sec {
Some(GlobalSec(globals)) =>
for global in globals {
let Global(_, init) = global
mark_declared_ref_funcs_in_expr(init, declared)
}
None => ()
}
match mod.table_sec {
Some(TableSec(tables)) =>
for table in tables {
let Table(_, init_opt) = table
match init_opt {
Some(init) => mark_declared_ref_funcs_in_expr(init, declared)
None => ()
}
}
None => ()
}
match mod.elem_sec {
Some(ElemSec(elems)) =>
for elem in elems {
let Elem(_, kind) = elem
match kind {
FuncsElemKind(funcs) =>
for f in funcs {
mark_declared_func(declared, f)
}
FuncExprsElemKind(exprs) | TypedExprsElemKind(_, exprs) =>
for expr in exprs {
mark_declared_ref_funcs_in_expr(expr, declared)
}
}
}
None => ()
}
declared
}
///|
fn is_declared_ref_func(declared : @bitset.BitSet, f : FuncIdx) -> Bool {
let FuncIdx(i) = f
match bounded_index(declared.length(), i.reinterpret_as_int()) {
Some(ii) => declared[ii]
None => false
}
}
///|
// Successful recursive scans carry no payload. Keep the Result wrapper at
// the expression boundary instead of allocating an Ok(unit) per instruction.
fn validate_ref_func_declarations_in_instr(
instr : Instruction,
env : Env,
declared : @bitset.BitSet,
) -> String? {
match instr {
RefFunc(f) => {
if env.has_func(f) && !is_declared_ref_func(declared, f) {
return Some("undeclared function reference: \{f}")
}
None
}
Block(_, Expr(body)) | Loop(_, Expr(body)) => {
for inner in body {
if validate_ref_func_declarations_in_instr(inner, env, declared)
is Some(e) {
return Some(e)
}
}
None
}
If(_, then_body, else_body) => {
for inner in then_body {
if validate_ref_func_declarations_in_instr(inner, env, declared)
is Some(e) {
return Some(e)
}
}
match else_body {
Some(branch) =>
for inner in branch {
if validate_ref_func_declarations_in_instr(inner, env, declared)
is Some(e) {
return Some(e)
}
}
None => ()
}
None
}
Try(_, Expr(body), catches, _) => {
for inner in body {
if validate_ref_func_declarations_in_instr(inner, env, declared)
is Some(e) {
return Some(e)
}
}
for catch_ in catches {
let Expr(catch_body) = match catch_ {
LegacyCatch(_, catch_body) | LegacyCatchAll(catch_body) => catch_body
}
for inner in catch_body {
if validate_ref_func_declarations_in_instr(inner, env, declared)
is Some(e) {
return Some(e)
}
}
}
None
}
TryTable(_, _, Expr(body)) => {
for inner in body {
if validate_ref_func_declarations_in_instr(inner, env, declared)
is Some(e) {
return Some(e)
}
}
None
}
_ => None
}
}
///|
fn validate_ref_func_declarations_in_expr(
expr : Expr,
env : Env,
declared : @bitset.BitSet,
) -> Result[Unit, String] {
let Expr(instrs) = expr
for instr in instrs {
if validate_ref_func_declarations_in_instr(instr, env, declared) is Some(e) {
return Err(e)
}
}
Ok(())
}
///|
fn validate_ref_func_declarations_in_module(
mod : Module,
env : Env,
imported_func_count : Int32,
declared : @bitset.BitSet,
trace~ : (String) -> Unit = validate_trace_noop,
trace_all_funcs~ : Bool = false,
trace_enabled~ : Bool = false,
) -> Result[Unit, ValidationDiagnostic] {
let globals_count = match mod.global_sec {
Some(GlobalSec(globals)) => globals.length()
None => 0
}
let globals_start_ms = if trace_enabled {
trace("phase=ref_func_declarations:globals:start count=\{globals_count}")
validate_trace_now_ms()
} else {
0UL
}
match mod.global_sec {
Some(GlobalSec(globals)) =>
for global in globals {
let Global(_, init) = global
if validate_ref_func_declarations_in_expr(init, env, declared) is Err(e) {
return Err(
ValidationDiagnostic::new(ValidationIssue::GlobalSection(e)),
)
}
}
None => ()
}
if trace_enabled {
trace(
"phase=ref_func_declarations:globals:done elapsed_ms=\{validate_trace_elapsed_ms(globals_start_ms)}",
)
}
let tables_count = match mod.table_sec {
Some(TableSec(tables)) => tables.length()
None => 0
}
let tables_start_ms = if trace_enabled {
trace("phase=ref_func_declarations:tables:start count=\{tables_count}")
validate_trace_now_ms()
} else {
0UL
}
match mod.table_sec {
Some(TableSec(tables)) =>
for table in tables {
let Table(_, init_opt) = table
match init_opt {
Some(init) =>
if validate_ref_func_declarations_in_expr(init, env, declared)
is Err(e) {
return Err(
ValidationDiagnostic::new(ValidationIssue::TableSection(e)),
)
}
None => ()
}
}
None => ()
}
if trace_enabled {
trace(
"phase=ref_func_declarations:tables:done elapsed_ms=\{validate_trace_elapsed_ms(tables_start_ms)}",
)
}
let elems_count = match mod.elem_sec {
Some(ElemSec(elems)) => elems.length()
None => 0
}
let elems_start_ms = if trace_enabled {
trace("phase=ref_func_declarations:elems:start count=\{elems_count}")
validate_trace_now_ms()
} else {
0UL
}
match mod.elem_sec {
Some(ElemSec(elems)) =>
for elem in elems {
let Elem(_, kind) = elem
match kind {
FuncsElemKind(_) => ()
FuncExprsElemKind(exprs) | TypedExprsElemKind(_, exprs) =>
for expr in exprs {
if validate_ref_func_declarations_in_expr(expr, env, declared)
is Err(e) {
return Err(
ValidationDiagnostic::new(ValidationIssue::ElementSection(e)),
)
}
}
}
}
None => ()
}
if trace_enabled {
trace(
"phase=ref_func_declarations:elems:done elapsed_ms=\{validate_trace_elapsed_ms(elems_start_ms)}",
)
}
let code_func_count = match mod.code_sec {
Some(CodeSec(bodies)) => bodies.length()
None => 0
}
let codesec_start_ms = if trace_enabled {
trace("phase=ref_func_declarations:codesec:start funcs=\{code_func_count}")
validate_trace_now_ms()
} else {
0UL
}
match (mod.code_sec, mod.func_sec) {
(Some(CodeSec(bodies)), Some(FuncSec(type_idxs))) => {
if bodies.length() != type_idxs.length() {
return Ok(())
}
for i in 0.. FuncIdx::new(func_abs.reinterpret_as_uint())
None => FuncIdx::new(i.reinterpret_as_uint())
}
let body_expr = match bodies[i] {
Func(_, expr) => expr
}
match validate_ref_func_declarations_in_expr(body_expr, env, declared) {
Ok(_) => ()
Err(t) =>
return Err(
ValidationDiagnostic::new(
ValidationIssue::FunctionBody(t),
func_idx=Some(func_idx),
),
)
}
if log_func {
trace("phase=ref_func_declarations:codesec func[\{ordinal}] done")
}
let scanned = i + 1
if trace_enabled &&
(
scanned % VALIDATE_REF_FUNC_DECL_TRACE_HEARTBEAT == 0 ||
scanned == bodies.length()
) {
trace(
"phase=ref_func_declarations:codesec:progress scanned=\{scanned} total=\{bodies.length()} elapsed_ms=\{validate_trace_elapsed_ms(codesec_start_ms)}",
)
}
}
if trace_enabled {
trace(
"phase=ref_func_declarations:codesec:done elapsed_ms=\{validate_trace_elapsed_ms(codesec_start_ms)}",
)
}
Ok(())
}
_ => {
if trace_enabled {
trace(
"phase=ref_func_declarations:codesec:done elapsed_ms=\{validate_trace_elapsed_ms(codesec_start_ms)}",
)
}
Ok(())
}
}
}
///|
/// Validates a whole module from an empty context, building its Env incrementally.
fn validate_trace_module_error(
trace : (String) -> Unit,
validate_start_ms : UInt64,
phase : String,
phase_start_ms : UInt64,
diag : ValidationDiagnostic,
trace_enabled : Bool,
) -> Result[Unit, ValidationDiagnostic] {
if !trace_enabled {
return Err(diag)
}
let phase_elapsed_ms = validate_trace_elapsed_ms(phase_start_ms)
validate_phase_timing_commit(phase, phase_elapsed_ms)
trace("phase=\{phase}:error elapsed_ms=\{phase_elapsed_ms} \{diag}")
validate_trace_emit_totals(trace)
trace(
"pass[validate_module]:error elapsed_ms=\{validate_trace_elapsed_ms(validate_start_ms)} \{diag}",
)
Err(diag)
}
///|
/// Validates a whole module from an empty context, building its Env incrementally.
fn validate_module_impl(
m : Module,
trace~ : (String) -> Unit = validate_trace_noop,
trace_all_funcs~ : Bool = false,
trace_enabled~ : Bool = false,
) -> Result[Unit, ValidationDiagnostic] {
if trace_enabled {
validate_helper_timing_reset()
validate_phase_timing_reset()
validate_hotspot_reset()
}
let {
type_sec: typesec_opt,
import_sec: importsec_opt,
func_sec: funcsec_opt,
table_sec: tablesec_opt,
mem_sec: memsec_opt,
tag_sec: tagsec_opt,
global_sec: globalsec_opt,
export_sec: exportsec_opt,
start_sec: startsec_opt,
elem_sec: elemsec_opt,
data_cnt_sec: datacntsec_opt,
code_sec: codesec_opt,
data_sec: datasec_opt,
..
} = m
let declared_funcs = match funcsec_opt {
Some(FuncSec(type_idxs)) => type_idxs.length()
None => 0
}
let code_bodies = match codesec_opt {
Some(CodeSec(bodies)) => bodies.length()
None => 0
}
let validate_start_ms = if trace_enabled {
validate_trace_now_ms()
} else {
0UL
}
if trace_enabled {
trace(
"pass[validate_module]:start declared_funcs=\{declared_funcs} code_bodies=\{code_bodies}",
)
}
// Start from empty env (module validation does not take an external env)
let mut env = empty_env()
// (1) Types: defines global type index space
let typesec_start_ms = validate_trace_phase_start(
trace, "typesec", trace_enabled,
)
env = match validate_typesec(typesec_opt, env) {
Ok(e) => {
validate_trace_phase_done(
trace, "typesec", typesec_start_ms, trace_enabled,
)
e
}
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"typesec",
typesec_start_ms,
ValidationDiagnostic::new(ValidationIssue::TypeSection(t)),
trace_enabled,
)
}
// (2) Imports: extend funcs/tables/mems/globals/tags index spaces
let importsec_start_ms = validate_trace_phase_start(
trace, "importsec", trace_enabled,
)
env = match validate_importsec(importsec_opt, env) {
Ok(e) => {
validate_trace_phase_done(
trace, "importsec", importsec_start_ms, trace_enabled,
)
e
}
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"importsec",
importsec_start_ms,
ValidationDiagnostic::new(ValidationIssue::ImportSection(t)),
trace_enabled,
)
}
let imported_func_count = env.funcs.length()
// (3) Function declarations (defined funcs): extend env.funcs
let funcsec_start_ms = validate_trace_phase_start(
trace, "funcsec", trace_enabled,
)
env = match validate_funcsec(funcsec_opt, env) {
Ok(e) => {
validate_trace_phase_done(
trace, "funcsec", funcsec_start_ms, trace_enabled,
)
e
}
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"funcsec",
funcsec_start_ms,
ValidationDiagnostic::new(ValidationIssue::FunctionSection(t)),
trace_enabled,
)
}
// (4) Tables / Memories / Tags / Globals: extend their index spaces
let tablesec_start_ms = validate_trace_phase_start(
trace, "tablesec", trace_enabled,
)
env = match validate_tablesec(tablesec_opt, env) {
Ok(e) => {
validate_trace_phase_done(
trace, "tablesec", tablesec_start_ms, trace_enabled,
)
e
}
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"tablesec",
tablesec_start_ms,
ValidationDiagnostic::new(ValidationIssue::TableSection(t)),
trace_enabled,
)
}
let memsec_start_ms = validate_trace_phase_start(
trace, "memsec", trace_enabled,
)
env = match validate_memsec(memsec_opt, env) {
Ok(e) => {
validate_trace_phase_done(trace, "memsec", memsec_start_ms, trace_enabled)
e
}
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"memsec",
memsec_start_ms,
ValidationDiagnostic::new(ValidationIssue::MemorySection(t)),
trace_enabled,
)
}
let tagsec_start_ms = validate_trace_phase_start(
trace, "tagsec", trace_enabled,
)
env = match validate_tagsec(tagsec_opt, env) {
Ok(e) => {
validate_trace_phase_done(trace, "tagsec", tagsec_start_ms, trace_enabled)
e
}
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"tagsec",
tagsec_start_ms,
ValidationDiagnostic::new(ValidationIssue::TagSection(t)),
trace_enabled,
)
}
let globalsec_start_ms = validate_trace_phase_start(
trace, "globalsec", trace_enabled,
)
env = match validate_globalsec(globalsec_opt, env) {
Ok(e) => {
validate_trace_phase_done(
trace, "globalsec", globalsec_start_ms, trace_enabled,
)
e
}
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"globalsec",
globalsec_start_ms,
ValidationDiagnostic::new(ValidationIssue::GlobalSection(t)),
trace_enabled,
)
}
// (5) Elements / Data: extend elems/datas (needed by instructions)
let elemsec_start_ms = validate_trace_phase_start(
trace, "elemsec", trace_enabled,
)
env = match validate_elemsec(elemsec_opt, env) {
Ok(e) => {
validate_trace_phase_done(
trace, "elemsec", elemsec_start_ms, trace_enabled,
)
e
}
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"elemsec",
elemsec_start_ms,
ValidationDiagnostic::new(ValidationIssue::ElementSection(t)),
trace_enabled,
)
}
let datasec_start_ms = validate_trace_phase_start(
trace, "datasec", trace_enabled,
)
env = match validate_datasec(datasec_opt, env) {
Ok(e) => {
validate_trace_phase_done(
trace, "datasec", datasec_start_ms, trace_enabled,
)
e
}
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"datasec",
datasec_start_ms,
ValidationDiagnostic::new(ValidationIssue::DataSection(t)),
trace_enabled,
)
}
// (6) DataCount section cross-check
let datacnt_start_ms = validate_trace_phase_start(
trace, "datacnt", trace_enabled,
)
match validate_datacnt(datacntsec_opt, datasec_opt) {
Ok(_) =>
validate_trace_phase_done(
trace, "datacnt", datacnt_start_ms, trace_enabled,
)
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"datacnt",
datacnt_start_ms,
ValidationDiagnostic::new(ValidationIssue::DataCountSection(t)),
trace_enabled,
)
}
let datacnt_required_start_ms = validate_trace_phase_start(
trace, "datacnt_requirement", trace_enabled,
)
match
validate_bulk_memory_data_count_requirement(
codesec_opt, datacntsec_opt, imported_func_count,
) {
Ok(_) =>
validate_trace_phase_done(
trace, "datacnt_requirement", datacnt_required_start_ms, trace_enabled,
)
Err(t) =>
return validate_trace_module_error(
trace, validate_start_ms, "datacnt_requirement", datacnt_required_start_ms,
t, trace_enabled,
)
}
// (7) Start function validation
let start_func_idx = match startsec_opt {
Some(StartSec(fidx)) => Some(fidx)
None => None
}
let startsec_start_ms = validate_trace_phase_start(
trace, "startsec", trace_enabled,
)
match validate_startsec(startsec_opt, env) {
Ok(_) =>
validate_trace_phase_done(
trace, "startsec", startsec_start_ms, trace_enabled,
)
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"startsec",
startsec_start_ms,
ValidationDiagnostic::new(
ValidationIssue::StartSection(t),
func_idx=start_func_idx,
),
trace_enabled,
)
}
// (8) Exports: validate indices and check name uniqueness
let exportsec_start_ms = validate_trace_phase_start(
trace, "exportsec", trace_enabled,
)
match validate_exportsec_unique(exportsec_opt, env) {
Ok(_) =>
validate_trace_phase_done(
trace, "exportsec", exportsec_start_ms, trace_enabled,
)
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"exportsec",
exportsec_start_ms,
ValidationDiagnostic::new(ValidationIssue::ExportSection(t)),
trace_enabled,
)
}
let ref_func_decl_start_ms = validate_trace_phase_start(
trace, "ref_func_declarations", trace_enabled,
)
let declared_funcs = collect_declared_funcs_bitmap(m, env)
match
validate_ref_func_declarations_in_module(
m,
env,
imported_func_count,
declared_funcs,
trace~,
trace_all_funcs~,
trace_enabled~,
) {
Ok(_) =>
validate_trace_phase_done(
trace, "ref_func_declarations", ref_func_decl_start_ms, trace_enabled,
)
Err(t) =>
return validate_trace_module_error(
trace, validate_start_ms, "ref_func_declarations", ref_func_decl_start_ms,
t, trace_enabled,
)
}
// (9) Code: typecheck each function body against env.funcs
let codesec_start_ms = validate_trace_phase_start(
trace, "codesec", trace_enabled,
)
match
validate_codesec_diag(
codesec_opt,
funcsec_opt,
env,
trace~,
trace_all_funcs~,
helper_timing_enabled=trace_enabled,
) {
Ok(_) =>
validate_trace_phase_done(
trace, "codesec", codesec_start_ms, trace_enabled,
)
Err(t) =>
return validate_trace_module_error(
trace, validate_start_ms, "codesec", codesec_start_ms, t, trace_enabled,
)
}
let namesec_start_ms = validate_trace_phase_start(
trace, "namesec", trace_enabled,
)
match validate_name_sec(m, env, imported_func_count) {
Ok(_) =>
validate_trace_phase_done(
trace, "namesec", namesec_start_ms, trace_enabled,
)
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"namesec",
namesec_start_ms,
ValidationDiagnostic::new(ValidationIssue::NameSection(t)),
trace_enabled,
)
}
if module_has_compiler_fact_metadata(m) {
let compiler_facts_start_ms = validate_trace_phase_start(
trace, "compiler_facts", trace_enabled,
)
match validate_compiler_fact_module_section(m, env, imported_func_count) {
Ok(_) =>
validate_trace_phase_done(
trace, "compiler_facts", compiler_facts_start_ms, trace_enabled,
)
Err(t) =>
return validate_trace_module_error(
trace,
validate_start_ms,
"compiler_facts",
compiler_facts_start_ms,
ValidationDiagnostic::new(ValidationIssue::CompilerFactsSection(t)),
trace_enabled,
)
}
}
if trace_enabled {
validate_trace_emit_totals(trace)
trace(
"pass[validate_module]:done elapsed_ms=\{validate_trace_elapsed_ms(validate_start_ms)}",
)
}
Ok(())
}
///|
fn validate_module_feature_policy(
m : Module,
disabled_features : Array[ProposalFeature],
) -> Result[Unit, ValidationError] {
if !disabled_features.is_empty() {
for feature in module_proposal_features(m) {
if disabled_features.contains(feature) {
return Err(
ValidationError::Validation(
ValidationDiagnostic::new(
FeaturePolicy(
"module requires disabled feature " +
proposal_feature_name(feature),
),
),
),
)
}
}
}
Ok(())
}
///|
pub fn validate_module_with_trace(
m : Module,
trace : (String) -> Unit,
trace_all_funcs~ : Bool = false,
disabled_features~ : Array[ProposalFeature] = [],
) -> Result[Unit, ValidationError] {
match validate_module_feature_policy(m, disabled_features) {
Ok(_) => ()
Err(err) => return Err(err)
}
match validate_module_impl(m, trace~, trace_all_funcs~, trace_enabled=true) {
Ok(_) => Ok(())
Err(diag) => Err(ValidationError::Validation(diag))
}
}
///|
pub fn validate_module(
m : Module,
disabled_features~ : Array[ProposalFeature] = [],
) -> Result[Unit, ValidationError] {
match validate_module_feature_policy(m, disabled_features) {
Ok(_) => ()
Err(err) => return Err(err)
}
match validate_module_impl(m) {
Ok(_) => Ok(())
Err(diag) => Err(ValidationError::Validation(diag))
}
}
///|
fn validate_fuzz_ascii_lower(input : String) -> String {
let out = StringBuilder::new()
for c in input {
if c >= 'A' && c <= 'Z' {
out.write_char((c.to_int() + 32).unsafe_to_char())
} else {
out.write_char(c)
}
}
out.to_string()
}
///|
pub struct ValidateValidRunConfig {
profile : String
attempts : Int32
generator_config : GenValidConfig
feature_floors : Array[ValidateValidFeatureFloor]
} derive(Eq, Debug)
///|
pub enum ValidateValidFeatureKey {
Imports
FuncExports
TableExports
MemExports
GlobalExports
TagExports
Starts
LacksStart
Tables
Mems
Globals
Tags
Elems
Datas
DataCount
RefTypes
V128
Calls
CallIndirect
BranchHeavyControl
PayloadControlFlow
MultivalueControlFlow
NumericFullOps
BrTable
StandaloneUnreachable
LocalTee
TypedSelect
TailCalls
ScalarMemoryWidths
NonzeroMemarg
MemoryLimitVariants
TableLimitVariants
ConstExprVariants
BasicRefInstructions
I31ExternConversions
GcConstructors
GcAccessors
StringOps
ExceptionTryTableMatrix
SimdPhase1
SimdPhase2
SimdPhase3
Descriptors
Continuations
Waitqueues
Atomics
ArrayMemory
SubtypingTopology
RichGcFieldPlans
ImportExportTopology
ElementSegmentRange
DataSegmentRange
NameCustomSections
GcExceptionCombo
GcTailCallCombo
SharedMemoryAtomics
ImportsStartExports
SemanticEffects
ImportEventChannel
TrapFrontier
ResourceObservation
PoisonedDeadCode
InvalidAstStrategies
InvalidBinaryStrategies
WastArbitraryParity
SsaStraightLineLocalChain
SsaAlreadySsaLocal
SsaMultiDefSameLocal
SsaParamEntryRead
SsaLocalTeeDefUse
SsaTeeFreshenCandidate
SsaTeeMergeFeedingCandidate
SsaTeeChildExpression
SsaParamWrite
SsaDeadLocalSet
SsaDeadLocalTee
SsaIfJoinPhiCandidate
SsaLoopBackedgePhiCandidate
SsaBrTablePhiCandidate
SsaParallelCopyCandidate
SsaParallelCopyCycleCandidate
SsaDefaultLocalMerge
SsaOneArmDefaultMerge
SsaOneArmParamMerge
SsaBothArmLocalMerge
SsaLoopCarriedLocalMerge
SsaMixedFreshCanonicalWrites
SsaUnreachableLocalCarrier
SsaDroppedIncrementGuardShape
SsaDroppedIncrementReversedBoundary
SsaExceptionalFailClosedShape
SsaTypedLoopParamFailClosedShape
SsaNestedCfgFailClosedShape
SsaNestedResultIfSupportedShape
SsaNestedResultIfConditionWrite
SsaNestedResultIfConditionValueBlock
SsaNestedResultIfArmValueBlock
SsaNestedResultIfMixedArmValueBlock
SsaNestedResultIfScalarResult
SsaStructuredEarlyReturnLocal
SsaBlockBranchMergeCopy
SsaBlockBrIfMergeCopy
SsaStructuredParamMerge
SsaBranchLocalNotReadLater
SsaDefaultLocalReadAfterBranchyControl
SsaDroppedUnreachableDebrisBeforeHardUnreachable
SsaCanonicalBodyLocalNextRead
SsaImmediateBlockExitCanonicalSetBr
SsaEnclosingLabelCopyBeforeExit
SsaBranchBlockAliasCopy
SsaNestedEnclosingLabelCopy
SsaParallelBranchCarrierCopy
SsaTeeBranchCarrierCopy
SsaVoidBrTableCanonicalCopies
SsaBranchOperandLocalBoundary
SsaNestedLoopTargetBoundary
SsaLargeStructuredLocalMix
} derive(Eq, Debug)
///|
pub impl Show for ValidateValidFeatureKey with fn output(self, logger) {
match self {
Imports => logger.write_string("Imports")
FuncExports => logger.write_string("FuncExports")
TableExports => logger.write_string("TableExports")
MemExports => logger.write_string("MemExports")
GlobalExports => logger.write_string("GlobalExports")
TagExports => logger.write_string("TagExports")
Starts => logger.write_string("Starts")
LacksStart => logger.write_string("LacksStart")
Tables => logger.write_string("Tables")
Mems => logger.write_string("Mems")
Globals => logger.write_string("Globals")
Tags => logger.write_string("Tags")
Elems => logger.write_string("Elems")
Datas => logger.write_string("Datas")
DataCount => logger.write_string("DataCount")
RefTypes => logger.write_string("RefTypes")
V128 => logger.write_string("V128")
Calls => logger.write_string("Calls")
CallIndirect => logger.write_string("CallIndirect")
BranchHeavyControl => logger.write_string("BranchHeavyControl")
PayloadControlFlow => logger.write_string("PayloadControlFlow")
MultivalueControlFlow => logger.write_string("MultivalueControlFlow")
NumericFullOps => logger.write_string("NumericFullOps")
BrTable => logger.write_string("BrTable")
StandaloneUnreachable => logger.write_string("StandaloneUnreachable")
LocalTee => logger.write_string("LocalTee")
TypedSelect => logger.write_string("TypedSelect")
TailCalls => logger.write_string("TailCalls")
ScalarMemoryWidths => logger.write_string("ScalarMemoryWidths")
NonzeroMemarg => logger.write_string("NonzeroMemarg")
MemoryLimitVariants => logger.write_string("MemoryLimitVariants")
TableLimitVariants => logger.write_string("TableLimitVariants")
ConstExprVariants => logger.write_string("ConstExprVariants")
BasicRefInstructions => logger.write_string("BasicRefInstructions")
I31ExternConversions => logger.write_string("I31ExternConversions")
GcConstructors => logger.write_string("GcConstructors")
GcAccessors => logger.write_string("GcAccessors")
StringOps => logger.write_string("StringOps")
ExceptionTryTableMatrix => logger.write_string("ExceptionTryTableMatrix")
SimdPhase1 => logger.write_string("SimdPhase1")
SimdPhase2 => logger.write_string("SimdPhase2")
SimdPhase3 => logger.write_string("SimdPhase3")
Descriptors => logger.write_string("Descriptors")
Continuations => logger.write_string("Continuations")
Waitqueues => logger.write_string("Waitqueues")
Atomics => logger.write_string("Atomics")
ArrayMemory => logger.write_string("ArrayMemory")
SubtypingTopology => logger.write_string("SubtypingTopology")
RichGcFieldPlans => logger.write_string("RichGcFieldPlans")
ImportExportTopology => logger.write_string("ImportExportTopology")
ElementSegmentRange => logger.write_string("ElementSegmentRange")
DataSegmentRange => logger.write_string("DataSegmentRange")
NameCustomSections => logger.write_string("NameCustomSections")
GcExceptionCombo => logger.write_string("GcExceptionCombo")
GcTailCallCombo => logger.write_string("GcTailCallCombo")
SharedMemoryAtomics => logger.write_string("SharedMemoryAtomics")
ImportsStartExports => logger.write_string("ImportsStartExports")
SemanticEffects => logger.write_string("SemanticEffects")
ImportEventChannel => logger.write_string("ImportEventChannel")
TrapFrontier => logger.write_string("TrapFrontier")
ResourceObservation => logger.write_string("ResourceObservation")
PoisonedDeadCode => logger.write_string("PoisonedDeadCode")
InvalidAstStrategies => logger.write_string("InvalidAstStrategies")
InvalidBinaryStrategies => logger.write_string("InvalidBinaryStrategies")
WastArbitraryParity => logger.write_string("WastArbitraryParity")
SsaStraightLineLocalChain =>
logger.write_string("SsaStraightLineLocalChain")
SsaAlreadySsaLocal => logger.write_string("SsaAlreadySsaLocal")
SsaMultiDefSameLocal => logger.write_string("SsaMultiDefSameLocal")
SsaParamEntryRead => logger.write_string("SsaParamEntryRead")
SsaLocalTeeDefUse => logger.write_string("SsaLocalTeeDefUse")
SsaTeeFreshenCandidate => logger.write_string("SsaTeeFreshenCandidate")
SsaTeeMergeFeedingCandidate =>
logger.write_string("SsaTeeMergeFeedingCandidate")
SsaTeeChildExpression => logger.write_string("SsaTeeChildExpression")
SsaParamWrite => logger.write_string("SsaParamWrite")
SsaDeadLocalSet => logger.write_string("SsaDeadLocalSet")
SsaDeadLocalTee => logger.write_string("SsaDeadLocalTee")
SsaIfJoinPhiCandidate => logger.write_string("SsaIfJoinPhiCandidate")
SsaLoopBackedgePhiCandidate =>
logger.write_string("SsaLoopBackedgePhiCandidate")
SsaBrTablePhiCandidate => logger.write_string("SsaBrTablePhiCandidate")
SsaParallelCopyCandidate => logger.write_string("SsaParallelCopyCandidate")
SsaParallelCopyCycleCandidate =>
logger.write_string("SsaParallelCopyCycleCandidate")
SsaDefaultLocalMerge => logger.write_string("SsaDefaultLocalMerge")
SsaOneArmDefaultMerge => logger.write_string("SsaOneArmDefaultMerge")
SsaOneArmParamMerge => logger.write_string("SsaOneArmParamMerge")
SsaBothArmLocalMerge => logger.write_string("SsaBothArmLocalMerge")
SsaLoopCarriedLocalMerge => logger.write_string("SsaLoopCarriedLocalMerge")
SsaMixedFreshCanonicalWrites =>
logger.write_string("SsaMixedFreshCanonicalWrites")
SsaUnreachableLocalCarrier =>
logger.write_string("SsaUnreachableLocalCarrier")
SsaDroppedIncrementGuardShape =>
logger.write_string("SsaDroppedIncrementGuardShape")
SsaDroppedIncrementReversedBoundary =>
logger.write_string("SsaDroppedIncrementReversedBoundary")
SsaExceptionalFailClosedShape =>
logger.write_string("SsaExceptionalFailClosedShape")
SsaTypedLoopParamFailClosedShape =>
logger.write_string("SsaTypedLoopParamFailClosedShape")
SsaNestedCfgFailClosedShape =>
logger.write_string("SsaNestedCfgFailClosedShape")
SsaNestedResultIfSupportedShape =>
logger.write_string("SsaNestedResultIfSupportedShape")
SsaNestedResultIfConditionWrite =>
logger.write_string("SsaNestedResultIfConditionWrite")
SsaNestedResultIfConditionValueBlock =>
logger.write_string("SsaNestedResultIfConditionValueBlock")
SsaNestedResultIfArmValueBlock =>
logger.write_string("SsaNestedResultIfArmValueBlock")
SsaNestedResultIfMixedArmValueBlock =>
logger.write_string("SsaNestedResultIfMixedArmValueBlock")
SsaNestedResultIfScalarResult =>
logger.write_string("SsaNestedResultIfScalarResult")
SsaStructuredEarlyReturnLocal =>
logger.write_string("SsaStructuredEarlyReturnLocal")
SsaBlockBranchMergeCopy => logger.write_string("SsaBlockBranchMergeCopy")
SsaBlockBrIfMergeCopy => logger.write_string("SsaBlockBrIfMergeCopy")
SsaStructuredParamMerge => logger.write_string("SsaStructuredParamMerge")
SsaBranchLocalNotReadLater =>
logger.write_string("SsaBranchLocalNotReadLater")
SsaDefaultLocalReadAfterBranchyControl =>
logger.write_string("SsaDefaultLocalReadAfterBranchyControl")
SsaDroppedUnreachableDebrisBeforeHardUnreachable =>
logger.write_string("SsaDroppedUnreachableDebrisBeforeHardUnreachable")
SsaCanonicalBodyLocalNextRead =>
logger.write_string("SsaCanonicalBodyLocalNextRead")
SsaImmediateBlockExitCanonicalSetBr =>
logger.write_string("SsaImmediateBlockExitCanonicalSetBr")
SsaEnclosingLabelCopyBeforeExit =>
logger.write_string("SsaEnclosingLabelCopyBeforeExit")
SsaBranchBlockAliasCopy => logger.write_string("SsaBranchBlockAliasCopy")
SsaNestedEnclosingLabelCopy =>
logger.write_string("SsaNestedEnclosingLabelCopy")
SsaParallelBranchCarrierCopy =>
logger.write_string("SsaParallelBranchCarrierCopy")
SsaTeeBranchCarrierCopy => logger.write_string("SsaTeeBranchCarrierCopy")
SsaVoidBrTableCanonicalCopies =>
logger.write_string("SsaVoidBrTableCanonicalCopies")
SsaBranchOperandLocalBoundary =>
logger.write_string("SsaBranchOperandLocalBoundary")
SsaNestedLoopTargetBoundary =>
logger.write_string("SsaNestedLoopTargetBoundary")
SsaLargeStructuredLocalMix =>
logger.write_string("SsaLargeStructuredLocalMix")
}
}
///|
pub struct ValidateValidFeatureFloor {
key : ValidateValidFeatureKey
min_count : Int32
} derive(Eq, Debug)
///|
pub impl Show for ValidateValidFeatureFloor with fn output(self, logger) {
logger.write_string("ValidateValidFeatureFloor { key: ")
self.key.output(logger)
logger.write_string(", min_count: ")
self.min_count.output(logger)
logger.write_char('}')
}
///|
pub struct ValidateValidFeatureFloorFailure {
key : ValidateValidFeatureKey
min_count : Int32
actual_count : Int32
} derive(Eq, Debug)
///|
pub enum ValidateValidFeatureLedgerStatus {
Covered
MissingOptional
MissingRequired
} derive(Eq, Debug)
///|
pub impl Show for ValidateValidFeatureLedgerStatus with fn output(self, logger) {
match self {
Covered => logger.write_string("Covered")
MissingOptional => logger.write_string("MissingOptional")
MissingRequired => logger.write_string("MissingRequired")
}
}
///|
pub struct ValidateValidFeatureLedgerEntry {
key : ValidateValidFeatureKey
label : String
actual_count : Int32
required_min : Int32
status : ValidateValidFeatureLedgerStatus
} derive(Eq, Debug)
///|
pub impl Show for ValidateValidFeatureFloorFailure with fn output(self, logger) {
logger.write_string("ValidateValidFeatureFloorFailure { key: ")
self.key.output(logger)
logger.write_string(", min_count: ")
self.min_count.output(logger)
logger.write_string(", actual_count: ")
self.actual_count.output(logger)
logger.write_char('}')
}
///|
pub struct ValidateValidFuzzStats {
profile : String
seed : UInt64
attempts : Int32
validated : Int32
generator_config : GenValidConfig
feature_stats : GenValidFeatureStats
feature_floors : Array[ValidateValidFeatureFloor]
} derive(Eq, Debug)
///|
pub impl Show for ValidateValidFuzzStats with fn output(self, logger) {
logger.write_string("ValidateValidFuzzStats {profile: ")
self.profile.output(logger)
logger.write_string(", seed: ")
self.seed.output(logger)
logger.write_string(", attempts: ")
self.attempts.output(logger)
logger.write_string(", validated: ")
self.validated.output(logger)
logger.write_string(", generator_config: ")
self.generator_config.output(logger)
logger.write_string(", feature_stats: ")
self.feature_stats.output(logger)
logger.write_string(", feature_floors: ")
self.feature_floors.output(logger)
logger.write_char('}')
}
///|
pub fn validate_valid_feature_floor(
key : ValidateValidFeatureKey,
min_count : Int32,
) -> ValidateValidFeatureFloor {
{ key, min_count, }
}
///|
pub fn validate_valid_feature_actual_count(
stats : GenValidFeatureStats,
key : ValidateValidFeatureKey,
) -> Int32 {
match key {
Imports => stats.imports
FuncExports => stats.func_exports
TableExports => stats.table_exports
MemExports => stats.mem_exports
GlobalExports => stats.global_exports
TagExports => stats.tag_exports
Starts => stats.starts
LacksStart => stats.lacks_start
Tables => stats.tables
Mems => stats.mems
Globals => stats.globals
Tags => stats.tags
Elems => stats.elems
Datas => stats.datas
DataCount => stats.data_count
RefTypes => stats.ref_types
V128 => stats.v128
Calls => stats.calls
CallIndirect => stats.call_indirect
BranchHeavyControl => stats.branch_heavy_control
PayloadControlFlow => stats.payload_control_flow
MultivalueControlFlow => stats.multivalue_control_flow
NumericFullOps => stats.numeric_full_ops
BrTable => stats.br_table
StandaloneUnreachable => stats.standalone_unreachable
LocalTee => stats.local_tee
TypedSelect => stats.typed_select
TailCalls => stats.tail_calls
ScalarMemoryWidths => stats.scalar_memory_widths
NonzeroMemarg => stats.nonzero_memarg
MemoryLimitVariants => stats.memory_limit_variants
TableLimitVariants => stats.table_limit_variants
ConstExprVariants => stats.const_expr_variants
BasicRefInstructions => stats.basic_ref_instructions
I31ExternConversions => stats.i31_extern_conversions
GcConstructors => stats.gc_constructors
GcAccessors => stats.gc_accessors
StringOps => stats.string_ops
ExceptionTryTableMatrix => stats.exception_try_table_matrix
SimdPhase1 => stats.simd_phase1
SimdPhase2 => stats.simd_phase2
SimdPhase3 => stats.simd_phase3
Descriptors => stats.descriptors
Continuations => stats.continuations
Waitqueues => stats.waitqueues
Atomics => stats.atomics
ArrayMemory => stats.array_memory
SubtypingTopology => stats.subtyping_topology
RichGcFieldPlans => stats.rich_gc_field_plans
ImportExportTopology => stats.import_export_topology
ElementSegmentRange => stats.element_segment_range
DataSegmentRange => stats.data_segment_range
NameCustomSections => stats.name_custom_sections
GcExceptionCombo => stats.gc_exception_combo
GcTailCallCombo => stats.gc_tail_call_combo
SharedMemoryAtomics => stats.shared_memory_atomics
ImportsStartExports => stats.imports_start_exports
SemanticEffects => stats.semantic_effects
ImportEventChannel => stats.import_event_channel
TrapFrontier => stats.trap_frontier
ResourceObservation => stats.resource_observation
PoisonedDeadCode => stats.poisoned_dead_code
InvalidAstStrategies => 0
InvalidBinaryStrategies => 0
WastArbitraryParity => 0
SsaStraightLineLocalChain => stats.ssa_straight_line_local_chain
SsaAlreadySsaLocal => stats.ssa_already_ssa_local
SsaMultiDefSameLocal => stats.ssa_multi_def_same_local
SsaParamEntryRead => stats.ssa_param_entry_read
SsaLocalTeeDefUse => stats.ssa_local_tee_def_use
SsaTeeFreshenCandidate => stats.ssa_tee_freshen_candidate
SsaTeeMergeFeedingCandidate => stats.ssa_tee_merge_feeding_candidate
SsaTeeChildExpression => stats.ssa_tee_child_expression
SsaParamWrite => stats.ssa_param_write
SsaDeadLocalSet => stats.ssa_dead_local_set
SsaDeadLocalTee => stats.ssa_dead_local_tee
SsaIfJoinPhiCandidate => stats.ssa_if_join_phi_candidate
SsaLoopBackedgePhiCandidate => stats.ssa_loop_backedge_phi_candidate
SsaBrTablePhiCandidate => stats.ssa_br_table_phi_candidate
SsaParallelCopyCandidate => stats.ssa_parallel_copy_candidate
SsaParallelCopyCycleCandidate => stats.ssa_parallel_copy_cycle_candidate
SsaDefaultLocalMerge => stats.ssa_default_local_merge
SsaOneArmDefaultMerge => stats.ssa_one_arm_default_merge
SsaOneArmParamMerge => stats.ssa_one_arm_param_merge
SsaBothArmLocalMerge => stats.ssa_both_arm_local_merge
SsaLoopCarriedLocalMerge => stats.ssa_loop_carried_local_merge
SsaMixedFreshCanonicalWrites => stats.ssa_mixed_fresh_canonical_writes
SsaUnreachableLocalCarrier => stats.ssa_unreachable_local_carrier
SsaDroppedIncrementGuardShape => stats.ssa_dropped_increment_guard_shape
SsaDroppedIncrementReversedBoundary =>
stats.ssa_dropped_increment_reversed_boundary
SsaExceptionalFailClosedShape => stats.ssa_exceptional_fail_closed_shape
SsaTypedLoopParamFailClosedShape =>
stats.ssa_typed_loop_param_fail_closed_shape
SsaNestedCfgFailClosedShape => stats.ssa_nested_cfg_fail_closed_shape
SsaNestedResultIfSupportedShape =>
stats.ssa_nested_result_if_supported_shape
SsaNestedResultIfConditionWrite =>
stats.ssa_nested_result_if_condition_write
SsaNestedResultIfConditionValueBlock =>
stats.ssa_nested_result_if_condition_value_block
SsaNestedResultIfArmValueBlock => stats.ssa_nested_result_if_arm_value_block
SsaNestedResultIfMixedArmValueBlock =>
stats.ssa_nested_result_if_mixed_arm_value_block
SsaNestedResultIfScalarResult => stats.ssa_nested_result_if_scalar_result
SsaStructuredEarlyReturnLocal => stats.ssa_structured_early_return_local
SsaBlockBranchMergeCopy => stats.ssa_block_branch_merge_copy
SsaBlockBrIfMergeCopy => stats.ssa_block_br_if_merge_copy
SsaStructuredParamMerge => stats.ssa_structured_param_merge
SsaBranchLocalNotReadLater => stats.ssa_branch_local_not_read_later
SsaDefaultLocalReadAfterBranchyControl =>
stats.ssa_default_local_read_after_branchy_control
SsaDroppedUnreachableDebrisBeforeHardUnreachable =>
stats.ssa_dropped_unreachable_debris_before_hard_unreachable
SsaCanonicalBodyLocalNextRead => stats.ssa_canonical_body_local_next_read
SsaImmediateBlockExitCanonicalSetBr =>
stats.ssa_immediate_block_exit_canonical_set_br
SsaEnclosingLabelCopyBeforeExit =>
stats.ssa_enclosing_label_copy_before_exit
SsaBranchBlockAliasCopy => stats.ssa_branch_block_alias_copy
SsaNestedEnclosingLabelCopy => stats.ssa_nested_enclosing_label_copy
SsaParallelBranchCarrierCopy => stats.ssa_parallel_branch_carrier_copy
SsaTeeBranchCarrierCopy => stats.ssa_tee_branch_carrier_copy
SsaVoidBrTableCanonicalCopies => stats.ssa_void_br_table_canonical_copies
SsaBranchOperandLocalBoundary => stats.ssa_branch_operand_local_boundary
SsaNestedLoopTargetBoundary => stats.ssa_nested_loop_target_boundary
SsaLargeStructuredLocalMix => stats.ssa_large_structured_local_mix
}
}
///|
fn validate_valid_feature_ledger_keys() -> Array[ValidateValidFeatureKey] {
[
Imports,
FuncExports,
TableExports,
MemExports,
GlobalExports,
TagExports,
Starts,
LacksStart,
Tables,
Mems,
Globals,
Tags,
Elems,
Datas,
DataCount,
RefTypes,
V128,
Calls,
CallIndirect,
BranchHeavyControl,
PayloadControlFlow,
MultivalueControlFlow,
NumericFullOps,
BrTable,
StandaloneUnreachable,
LocalTee,
TypedSelect,
TailCalls,
ScalarMemoryWidths,
NonzeroMemarg,
MemoryLimitVariants,
TableLimitVariants,
ConstExprVariants,
BasicRefInstructions,
I31ExternConversions,
GcConstructors,
GcAccessors,
StringOps,
ExceptionTryTableMatrix,
SimdPhase1,
SimdPhase2,
SimdPhase3,
Descriptors,
Continuations,
Waitqueues,
Atomics,
ArrayMemory,
SubtypingTopology,
RichGcFieldPlans,
ImportExportTopology,
ElementSegmentRange,
DataSegmentRange,
NameCustomSections,
GcExceptionCombo,
GcTailCallCombo,
SharedMemoryAtomics,
ImportsStartExports,
SemanticEffects,
ImportEventChannel,
TrapFrontier,
ResourceObservation,
PoisonedDeadCode,
InvalidAstStrategies,
InvalidBinaryStrategies,
WastArbitraryParity,
SsaStraightLineLocalChain,
SsaAlreadySsaLocal,
SsaMultiDefSameLocal,
SsaParamEntryRead,
SsaLocalTeeDefUse,
SsaTeeFreshenCandidate,
SsaTeeMergeFeedingCandidate,
SsaTeeChildExpression,
SsaParamWrite,
SsaDeadLocalSet,
SsaDeadLocalTee,
SsaIfJoinPhiCandidate,
SsaLoopBackedgePhiCandidate,
SsaBrTablePhiCandidate,
SsaParallelCopyCandidate,
SsaParallelCopyCycleCandidate,
SsaDefaultLocalMerge,
SsaOneArmDefaultMerge,
SsaOneArmParamMerge,
SsaBothArmLocalMerge,
SsaLoopCarriedLocalMerge,
SsaMixedFreshCanonicalWrites,
SsaUnreachableLocalCarrier,
SsaDroppedIncrementGuardShape,
SsaDroppedIncrementReversedBoundary,
SsaExceptionalFailClosedShape,
SsaTypedLoopParamFailClosedShape,
SsaNestedCfgFailClosedShape,
SsaNestedResultIfSupportedShape,
SsaNestedResultIfConditionWrite,
SsaNestedResultIfConditionValueBlock,
SsaNestedResultIfArmValueBlock,
SsaNestedResultIfMixedArmValueBlock,
SsaNestedResultIfScalarResult,
SsaStructuredEarlyReturnLocal,
SsaBlockBranchMergeCopy,
SsaBlockBrIfMergeCopy,
SsaStructuredParamMerge,
SsaBranchLocalNotReadLater,
SsaDefaultLocalReadAfterBranchyControl,
SsaDroppedUnreachableDebrisBeforeHardUnreachable,
SsaCanonicalBodyLocalNextRead,
SsaImmediateBlockExitCanonicalSetBr,
SsaEnclosingLabelCopyBeforeExit,
SsaBranchBlockAliasCopy,
SsaNestedEnclosingLabelCopy,
SsaParallelBranchCarrierCopy,
SsaTeeBranchCarrierCopy,
SsaVoidBrTableCanonicalCopies,
SsaBranchOperandLocalBoundary,
SsaNestedLoopTargetBoundary,
SsaLargeStructuredLocalMix,
]
}
///|
fn validate_valid_feature_required_min(
key : ValidateValidFeatureKey,
floors : Array[ValidateValidFeatureFloor],
) -> Int32 {
for floor in floors {
if floor.key == key {
return floor.min_count
}
}
0
}
///|
pub fn validate_valid_feature_ledger(
stats : GenValidFeatureStats,
floors : Array[ValidateValidFeatureFloor],
) -> Array[ValidateValidFeatureLedgerEntry] {
let entries : Array[ValidateValidFeatureLedgerEntry] = []
for key in validate_valid_feature_ledger_keys() {
let actual_count = validate_valid_feature_actual_count(stats, key)
let required_min = validate_valid_feature_required_min(key, floors)
let status = if actual_count < required_min {
MissingRequired
} else if actual_count > 0 {
Covered
} else {
MissingOptional
}
entries.push({
key,
label: validate_valid_feature_key_label(key),
actual_count,
required_min,
status,
})
}
entries
}
///|
pub fn check_validate_valid_feature_floors(
stats : GenValidFeatureStats,
floors : Array[ValidateValidFeatureFloor],
) -> Array[ValidateValidFeatureFloorFailure] {
let failures : Array[ValidateValidFeatureFloorFailure] = []
for floor in floors {
let actual_count = validate_valid_feature_actual_count(stats, floor.key)
if actual_count < floor.min_count {
failures.push({
key: floor.key,
min_count: floor.min_count,
actual_count,
})
}
}
failures
}
///|
fn validate_valid_feature_key_label(key : ValidateValidFeatureKey) -> String {
match key {
Imports => "imports"
FuncExports => "func_exports"
TableExports => "table_exports"
MemExports => "mem_exports"
GlobalExports => "global_exports"
TagExports => "tag_exports"
Starts => "starts"
LacksStart => "lacks_start"
Tables => "tables"
Mems => "mems"
Globals => "globals"
Tags => "tags"
Elems => "elems"
Datas => "datas"
DataCount => "data_count"
RefTypes => "ref_types"
V128 => "v128"
Calls => "calls"
CallIndirect => "call_indirect"
BranchHeavyControl => "branch_heavy_control"
PayloadControlFlow => "payload_control_flow"
MultivalueControlFlow => "multivalue_control_flow"
NumericFullOps => "numeric_full_ops"
BrTable => "br_table"
StandaloneUnreachable => "standalone_unreachable"
LocalTee => "local_tee"
TypedSelect => "typed_select"
TailCalls => "tail_calls"
ScalarMemoryWidths => "scalar_memory_widths"
NonzeroMemarg => "nonzero_memarg"
MemoryLimitVariants => "memory_limit_variants"
TableLimitVariants => "table_limit_variants"
ConstExprVariants => "const_expr_variants"
BasicRefInstructions => "basic_ref_instructions"
I31ExternConversions => "i31_extern_conversions"
GcConstructors => "gc_constructors"
GcAccessors => "gc_accessors"
StringOps => "string_ops"
ExceptionTryTableMatrix => "exception_try_table_matrix"
SimdPhase1 => "simd_phase1"
SimdPhase2 => "simd_phase2"
SimdPhase3 => "simd_phase3"
Descriptors => "descriptors"
Continuations => "continuations"
Waitqueues => "waitqueues"
Atomics => "atomics"
ArrayMemory => "array_memory"
SubtypingTopology => "subtyping_topology"
RichGcFieldPlans => "rich_gc_field_plans"
ImportExportTopology => "import_export_topology"
ElementSegmentRange => "element_segment_range"
DataSegmentRange => "data_segment_range"
NameCustomSections => "name_custom_sections"
GcExceptionCombo => "gc_exception_combo"
GcTailCallCombo => "gc_tail_call_combo"
SharedMemoryAtomics => "shared_memory_atomics"
ImportsStartExports => "imports_start_exports"
SemanticEffects => "semantic_effects"
ImportEventChannel => "import_event_channel"
TrapFrontier => "trap_frontier"
ResourceObservation => "resource_observation"
PoisonedDeadCode => "poisoned_dead_code"
InvalidAstStrategies => "invalid_ast_strategies"
InvalidBinaryStrategies => "invalid_binary_strategies"
WastArbitraryParity => "wast_arbitrary_parity"
SsaStraightLineLocalChain => "ssa-straight-line-local-chain"
SsaAlreadySsaLocal => "ssa-already-ssa-local"
SsaMultiDefSameLocal => "ssa-multi-def-same-local"
SsaParamEntryRead => "ssa-param-entry-read"
SsaLocalTeeDefUse => "ssa-local-tee-def-use"
SsaTeeFreshenCandidate => "ssa-tee-freshen-candidate"
SsaTeeMergeFeedingCandidate => "ssa-tee-merge-feeding-candidate"
SsaTeeChildExpression => "ssa-tee-child-expression"
SsaParamWrite => "ssa-param-write"
SsaDeadLocalSet => "ssa-dead-local-set"
SsaDeadLocalTee => "ssa-dead-local-tee"
SsaIfJoinPhiCandidate => "ssa-if-join-phi-candidate"
SsaLoopBackedgePhiCandidate => "ssa-loop-backedge-phi-candidate"
SsaBrTablePhiCandidate => "ssa-br-table-phi-candidate"
SsaParallelCopyCandidate => "ssa-parallel-copy-candidate"
SsaParallelCopyCycleCandidate => "ssa-parallel-copy-cycle-candidate"
SsaDefaultLocalMerge => "ssa-default-local-merge"
SsaOneArmDefaultMerge => "ssa-one-arm-default-merge"
SsaOneArmParamMerge => "ssa-one-arm-param-merge"
SsaBothArmLocalMerge => "ssa-both-arm-local-merge"
SsaLoopCarriedLocalMerge => "ssa-loop-carried-local-merge"
SsaMixedFreshCanonicalWrites => "ssa-mixed-fresh-canonical-writes"
SsaUnreachableLocalCarrier => "ssa-unreachable-local-carrier"
SsaDroppedIncrementGuardShape => "ssa-dropped-increment-guard-shape"
SsaDroppedIncrementReversedBoundary =>
"ssa-dropped-increment-reversed-boundary"
SsaExceptionalFailClosedShape => "ssa-exceptional-fail-closed-shape"
SsaTypedLoopParamFailClosedShape => "ssa-typed-loop-param-fail-closed-shape"
SsaNestedCfgFailClosedShape => "ssa-nested-cfg-fail-closed-shape"
SsaNestedResultIfSupportedShape => "ssa-nested-result-if-supported-shape"
SsaNestedResultIfConditionWrite => "ssa-nested-result-if-condition-write"
SsaNestedResultIfConditionValueBlock =>
"ssa-nested-result-if-condition-value-block"
SsaNestedResultIfArmValueBlock => "ssa-nested-result-if-arm-value-block"
SsaNestedResultIfMixedArmValueBlock =>
"ssa-nested-result-if-mixed-arm-value-block"
SsaNestedResultIfScalarResult => "ssa-nested-result-if-scalar-result"
SsaStructuredEarlyReturnLocal => "ssa-structured-early-return-local"
SsaBlockBranchMergeCopy => "ssa-block-branch-merge-copy"
SsaBlockBrIfMergeCopy => "ssa-block-br-if-merge-copy"
SsaStructuredParamMerge => "ssa-structured-param-merge"
SsaBranchLocalNotReadLater => "ssa-branch-local-not-read-later"
SsaDefaultLocalReadAfterBranchyControl =>
"ssa-default-local-read-after-branchy-control"
SsaDroppedUnreachableDebrisBeforeHardUnreachable =>
"ssa-dropped-unreachable-debris-before-hard-unreachable"
SsaCanonicalBodyLocalNextRead => "ssa-canonical-body-local-next-read"
SsaImmediateBlockExitCanonicalSetBr =>
"ssa-immediate-block-exit-canonical-set-br"
SsaEnclosingLabelCopyBeforeExit => "ssa-enclosing-label-copy-before-exit"
SsaBranchBlockAliasCopy => "ssa-branch-block-alias-copy"
SsaNestedEnclosingLabelCopy => "ssa-nested-enclosing-label-copy"
SsaParallelBranchCarrierCopy => "ssa-parallel-branch-carrier-copy"
SsaTeeBranchCarrierCopy => "ssa-tee-branch-carrier-copy"
SsaVoidBrTableCanonicalCopies => "ssa-void-br-table-canonical-copies"
SsaBranchOperandLocalBoundary => "ssa-branch-operand-local-boundary"
SsaNestedLoopTargetBoundary => "ssa-nested-loop-target-boundary"
SsaLargeStructuredLocalMix => "ssa-large-structured-local-mix"
}
}
///|
fn validate_valid_feature_key_from_label(
label : String,
) -> ValidateValidFeatureKey? {
match validate_fuzz_ascii_lower(label.trim().to_owned()) {
"imports" => Some(Imports)
"func_exports" => Some(FuncExports)
"table_exports" => Some(TableExports)
"mem_exports" => Some(MemExports)
"global_exports" => Some(GlobalExports)
"tag_exports" => Some(TagExports)
"starts" => Some(Starts)
"lacks_start" => Some(LacksStart)
"tables" => Some(Tables)
"mems" => Some(Mems)
"globals" => Some(Globals)
"tags" => Some(Tags)
"elems" => Some(Elems)
"datas" => Some(Datas)
"data_count" => Some(DataCount)
"ref_types" => Some(RefTypes)
"v128" => Some(V128)
"calls" => Some(Calls)
"call_indirect" => Some(CallIndirect)
"branch_heavy_control" => Some(BranchHeavyControl)
"payload_control_flow" => Some(PayloadControlFlow)
"multivalue_control_flow" => Some(MultivalueControlFlow)
"numeric_full_ops" => Some(NumericFullOps)
"br_table" => Some(BrTable)
"standalone_unreachable" => Some(StandaloneUnreachable)
"local_tee" => Some(LocalTee)
"typed_select" => Some(TypedSelect)
"tail_calls" => Some(TailCalls)
"scalar_memory_widths" => Some(ScalarMemoryWidths)
"nonzero_memarg" => Some(NonzeroMemarg)
"memory_limit_variants" => Some(MemoryLimitVariants)
"table_limit_variants" => Some(TableLimitVariants)
"const_expr_variants" => Some(ConstExprVariants)
"basic_ref_instructions" => Some(BasicRefInstructions)
"i31_extern_conversions" => Some(I31ExternConversions)
"gc_constructors" => Some(GcConstructors)
"gc_accessors" => Some(GcAccessors)
"string_ops" => Some(StringOps)
"exception_try_table_matrix" => Some(ExceptionTryTableMatrix)
"simd_phase1" => Some(SimdPhase1)
"simd_phase2" => Some(SimdPhase2)
"simd_phase3" => Some(SimdPhase3)
"descriptors" => Some(Descriptors)
"continuations" => Some(Continuations)
"waitqueues" => Some(Waitqueues)
"atomics" => Some(Atomics)
"array_memory" => Some(ArrayMemory)
"subtyping_topology" => Some(SubtypingTopology)
"rich_gc_field_plans" => Some(RichGcFieldPlans)
"import_export_topology" => Some(ImportExportTopology)
"element_segment_range" => Some(ElementSegmentRange)
"data_segment_range" => Some(DataSegmentRange)
"name_custom_sections" => Some(NameCustomSections)
"gc_exception_combo" => Some(GcExceptionCombo)
"gc_tail_call_combo" => Some(GcTailCallCombo)
"shared_memory_atomics" => Some(SharedMemoryAtomics)
"imports_start_exports" => Some(ImportsStartExports)
"semantic_effects" => Some(SemanticEffects)
"import_event_channel" => Some(ImportEventChannel)
"trap_frontier" => Some(TrapFrontier)
"resource_observation" => Some(ResourceObservation)
"poisoned_dead_code" => Some(PoisonedDeadCode)
"invalid_ast_strategies" => Some(InvalidAstStrategies)
"invalid_binary_strategies" => Some(InvalidBinaryStrategies)
"wast_arbitrary_parity" => Some(WastArbitraryParity)
"ssa-straight-line-local-chain" => Some(SsaStraightLineLocalChain)
"ssa-already-ssa-local" => Some(SsaAlreadySsaLocal)
"ssa-multi-def-same-local" => Some(SsaMultiDefSameLocal)
"ssa-param-entry-read" => Some(SsaParamEntryRead)
"ssa-local-tee-def-use" => Some(SsaLocalTeeDefUse)
"ssa-tee-freshen-candidate" => Some(SsaTeeFreshenCandidate)
"ssa-tee-merge-feeding-candidate" => Some(SsaTeeMergeFeedingCandidate)
"ssa-tee-child-expression" => Some(SsaTeeChildExpression)
"ssa-param-write" => Some(SsaParamWrite)
"ssa-dead-local-set" => Some(SsaDeadLocalSet)
"ssa-dead-local-tee" => Some(SsaDeadLocalTee)
"ssa-if-join-phi-candidate" => Some(SsaIfJoinPhiCandidate)
"ssa-loop-backedge-phi-candidate" => Some(SsaLoopBackedgePhiCandidate)
"ssa-br-table-phi-candidate" => Some(SsaBrTablePhiCandidate)
"ssa-parallel-copy-candidate" => Some(SsaParallelCopyCandidate)
"ssa-parallel-copy-cycle-candidate" => Some(SsaParallelCopyCycleCandidate)
"ssa-default-local-merge" => Some(SsaDefaultLocalMerge)
"ssa-one-arm-default-merge" => Some(SsaOneArmDefaultMerge)
"ssa-one-arm-param-merge" => Some(SsaOneArmParamMerge)
"ssa-both-arm-local-merge" => Some(SsaBothArmLocalMerge)
"ssa-loop-carried-local-merge" => Some(SsaLoopCarriedLocalMerge)
"ssa-mixed-fresh-canonical-writes" => Some(SsaMixedFreshCanonicalWrites)
"ssa-unreachable-local-carrier" => Some(SsaUnreachableLocalCarrier)
"ssa-dropped-increment-guard-shape" => Some(SsaDroppedIncrementGuardShape)
"ssa-dropped-increment-reversed-boundary" =>
Some(SsaDroppedIncrementReversedBoundary)
"ssa-exceptional-fail-closed-shape" => Some(SsaExceptionalFailClosedShape)
"ssa-typed-loop-param-fail-closed-shape" =>
Some(SsaTypedLoopParamFailClosedShape)
"ssa-nested-cfg-fail-closed-shape" => Some(SsaNestedCfgFailClosedShape)
"ssa-nested-result-if-supported-shape" =>
Some(SsaNestedResultIfSupportedShape)
"ssa-nested-result-if-condition-write" =>
Some(SsaNestedResultIfConditionWrite)
"ssa-nested-result-if-condition-value-block" =>
Some(SsaNestedResultIfConditionValueBlock)
"ssa-nested-result-if-arm-value-block" =>
Some(SsaNestedResultIfArmValueBlock)
"ssa-nested-result-if-mixed-arm-value-block" =>
Some(SsaNestedResultIfMixedArmValueBlock)
"ssa-nested-result-if-scalar-result" => Some(SsaNestedResultIfScalarResult)
"ssa-structured-early-return-local" => Some(SsaStructuredEarlyReturnLocal)
"ssa-block-branch-merge-copy" => Some(SsaBlockBranchMergeCopy)
"ssa-block-br-if-merge-copy" => Some(SsaBlockBrIfMergeCopy)
"ssa-structured-param-merge" => Some(SsaStructuredParamMerge)
"ssa-branch-local-not-read-later" => Some(SsaBranchLocalNotReadLater)
"ssa-default-local-read-after-branchy-control" =>
Some(SsaDefaultLocalReadAfterBranchyControl)
"ssa-dropped-unreachable-debris-before-hard-unreachable" =>
Some(SsaDroppedUnreachableDebrisBeforeHardUnreachable)
"ssa-canonical-body-local-next-read" => Some(SsaCanonicalBodyLocalNextRead)
"ssa-immediate-block-exit-canonical-set-br" =>
Some(SsaImmediateBlockExitCanonicalSetBr)
"ssa-enclosing-label-copy-before-exit" =>
Some(SsaEnclosingLabelCopyBeforeExit)
"ssa-branch-block-alias-copy" => Some(SsaBranchBlockAliasCopy)
"ssa-nested-enclosing-label-copy" => Some(SsaNestedEnclosingLabelCopy)
"ssa-parallel-branch-carrier-copy" => Some(SsaParallelBranchCarrierCopy)
"ssa-tee-branch-carrier-copy" => Some(SsaTeeBranchCarrierCopy)
"ssa-void-br-table-canonical-copies" => Some(SsaVoidBrTableCanonicalCopies)
"ssa-branch-operand-local-boundary" => Some(SsaBranchOperandLocalBoundary)
"ssa-nested-loop-target-boundary" => Some(SsaNestedLoopTargetBoundary)
"ssa-large-structured-local-mix" => Some(SsaLargeStructuredLocalMix)
_ => None
}
}
///|
pub fn validate_valid_feature_floor_by_label(
label : String,
min_count : Int32,
) -> Result[ValidateValidFeatureFloor, String] {
match validate_valid_feature_key_from_label(label) {
Some(key) => Ok(validate_valid_feature_floor(key, min_count))
None => Err("unknown validate-valid feature label '\{label}'")
}
}
///|
pub fn validate_valid_feature_actual_count_by_label(
stats : GenValidFeatureStats,
label : String,
) -> Result[Int32, String] {
match validate_valid_feature_key_from_label(label) {
Some(key) => Ok(validate_valid_feature_actual_count(stats, key))
None => Err("unknown validate-valid feature label '\{label}'")
}
}
///|
fn validate_valid_feature_floor_failures_message(
failures : Array[ValidateValidFeatureFloorFailure],
) -> String {
let out = StringBuilder::new()
let mut i = 0
while i < failures.length() {
if i > 0 {
out.write_string(", ")
}
let failure = failures[i]
out.write_string(
"\{validate_valid_feature_key_label(failure.key)} actual=\{failure.actual_count} min=\{failure.min_count}",
)
i += 1
}
out.to_string()
}
///|
fn validate_valid_fzg_feature_floors(
min_count : Int32,
) -> Array[ValidateValidFeatureFloor] {
[
validate_valid_feature_floor(NumericFullOps, min_count),
validate_valid_feature_floor(BrTable, min_count),
validate_valid_feature_floor(StandaloneUnreachable, min_count),
validate_valid_feature_floor(LocalTee, min_count),
validate_valid_feature_floor(TypedSelect, min_count),
validate_valid_feature_floor(TailCalls, min_count),
validate_valid_feature_floor(ScalarMemoryWidths, min_count),
validate_valid_feature_floor(NonzeroMemarg, min_count),
validate_valid_feature_floor(MemoryLimitVariants, min_count),
validate_valid_feature_floor(TableLimitVariants, min_count),
validate_valid_feature_floor(ConstExprVariants, min_count),
validate_valid_feature_floor(BasicRefInstructions, min_count),
validate_valid_feature_floor(I31ExternConversions, min_count),
validate_valid_feature_floor(GcConstructors, min_count),
validate_valid_feature_floor(GcAccessors, min_count),
validate_valid_feature_floor(StringOps, min_count),
validate_valid_feature_floor(ExceptionTryTableMatrix, min_count),
validate_valid_feature_floor(SimdPhase1, min_count),
validate_valid_feature_floor(SimdPhase2, min_count),
validate_valid_feature_floor(SimdPhase3, min_count),
validate_valid_feature_floor(Atomics, min_count),
validate_valid_feature_floor(SubtypingTopology, min_count),
validate_valid_feature_floor(RichGcFieldPlans, min_count),
validate_valid_feature_floor(ImportExportTopology, min_count),
validate_valid_feature_floor(ElementSegmentRange, min_count),
validate_valid_feature_floor(DataSegmentRange, min_count),
validate_valid_feature_floor(NameCustomSections, min_count),
validate_valid_feature_floor(GcExceptionCombo, min_count),
validate_valid_feature_floor(GcTailCallCombo, min_count),
validate_valid_feature_floor(SharedMemoryAtomics, min_count),
validate_valid_feature_floor(ImportsStartExports, min_count),
]
}
///|
fn validate_valid_append_feature_floors(
base : Array[ValidateValidFeatureFloor],
extra : Array[ValidateValidFeatureFloor],
) -> Array[ValidateValidFeatureFloor] {
let out : Array[ValidateValidFeatureFloor] = []
for floor in base {
out.push(floor)
}
for floor in extra {
out.push(floor)
}
out
}
///|
fn validate_valid_feature_floors_for_profile(
normalized : String,
) -> Array[ValidateValidFeatureFloor] {
match normalized {
"smoke" =>
validate_valid_append_feature_floors(
[
validate_valid_feature_floor(Imports, 1),
validate_valid_feature_floor(Starts, 1),
validate_valid_feature_floor(Tables, 1),
validate_valid_feature_floor(Mems, 1),
validate_valid_feature_floor(RefTypes, 1),
validate_valid_feature_floor(V128, 1),
validate_valid_feature_floor(Calls, 1),
validate_valid_feature_floor(CallIndirect, 1),
validate_valid_feature_floor(BranchHeavyControl, 1),
],
validate_valid_fzg_feature_floors(1),
)
"ci" =>
validate_valid_append_feature_floors(
[
validate_valid_feature_floor(Imports, 100),
validate_valid_feature_floor(TableExports, 100),
validate_valid_feature_floor(MemExports, 100),
validate_valid_feature_floor(GlobalExports, 250),
validate_valid_feature_floor(TagExports, 50),
validate_valid_feature_floor(Starts, 50),
validate_valid_feature_floor(Tables, 100),
validate_valid_feature_floor(Mems, 100),
validate_valid_feature_floor(Globals, 250),
validate_valid_feature_floor(Tags, 50),
validate_valid_feature_floor(Elems, 25),
validate_valid_feature_floor(Datas, 25),
validate_valid_feature_floor(DataCount, 25),
validate_valid_feature_floor(RefTypes, 500),
validate_valid_feature_floor(V128, 500),
validate_valid_feature_floor(Calls, 500),
validate_valid_feature_floor(CallIndirect, 100),
validate_valid_feature_floor(BranchHeavyControl, 250),
],
validate_valid_fzg_feature_floors(10),
)
"stress" =>
validate_valid_append_feature_floors(
[
validate_valid_feature_floor(Imports, 2000),
validate_valid_feature_floor(TableExports, 2000),
validate_valid_feature_floor(MemExports, 2000),
validate_valid_feature_floor(GlobalExports, 5000),
validate_valid_feature_floor(TagExports, 1000),
validate_valid_feature_floor(Starts, 1000),
validate_valid_feature_floor(Tables, 2000),
validate_valid_feature_floor(Mems, 2000),
validate_valid_feature_floor(Globals, 5000),
validate_valid_feature_floor(Tags, 1000),
validate_valid_feature_floor(Elems, 500),
validate_valid_feature_floor(Datas, 500),
validate_valid_feature_floor(DataCount, 500),
validate_valid_feature_floor(RefTypes, 10000),
validate_valid_feature_floor(V128, 10000),
validate_valid_feature_floor(Calls, 10000),
validate_valid_feature_floor(CallIndirect, 2000),
validate_valid_feature_floor(BranchHeavyControl, 5000),
],
validate_valid_fzg_feature_floors(100),
)
_ => abort("unreachable validate-valid profile")
}
}
///|
pub fn validate_valid_run_config(
profile : String,
) -> Result[ValidateValidRunConfig, String] {
let normalized = validate_fuzz_ascii_lower(profile.trim().to_owned())
let attempts = match normalized {
"smoke" => 128
"ci" => 5000
"stress" => 100000
_ =>
return Err(
"unknown validate-valid fuzz profile '\{profile}'; expected smoke|ci|stress",
)
}
let generator_config = match normalized {
"smoke" => GenValidConfig::for_profile(CoverageForcedAllFeaturesProfile)
"ci" =>
GenValidConfig::{
..GenValidConfig::for_profile(CoverageForcedAllFeaturesProfile),
max_defined_funcs: 5,
max_globals: 3,
max_body_statements: 8,
max_locals: 4,
}
"stress" => GenValidConfig::for_profile(ValidatorStressProfile)
_ => abort("unreachable validate-valid profile")
}
let feature_floors = validate_valid_feature_floors_for_profile(normalized)
Ok(ValidateValidRunConfig::{
profile: normalized,
attempts,
generator_config,
feature_floors,
})
}
///|
pub fn run_validate_valid_fuzz(
profile : String,
seed : UInt64,
) -> Result[ValidateValidFuzzStats, String] {
let run_config = match validate_valid_run_config(profile) {
Ok(v) => v
Err(msg) => return Err(msg)
}
let normalized = run_config.profile
let attempts = run_config.attempts
let generator_config = run_config.generator_config
let feature_floors = run_config.feature_floors
let rnd = @splitmix.new(seed~)
let mut validated = 0
let mut iteration = 0
let mut feature_stats = GenValidFeatureStats::empty(generator_config.mode)
let mut remaining_attempts = attempts
while remaining_attempts > 0 {
iteration += 1
let mod = gen_valid_module_with_config(rnd, generator_config)
feature_stats = feature_stats.record(
gen_valid_feature_facts(mod, generator_config.mode),
)
match validate_module(mod) {
Ok(_) => validated += 1
Err(err) =>
return Err(
"validate-valid fuzz failed: profile=\{normalized} seed=\{seed} iteration=\{iteration} error=\{validation_error_message(err)}",
)
}
remaining_attempts -= 1
}
let floor_failures = check_validate_valid_feature_floors(
feature_stats, feature_floors,
)
if !floor_failures.is_empty() {
return Err(
"validate-valid fuzz feature floors failed: profile=\{normalized} seed=\{seed} \{validate_valid_feature_floor_failures_message(floor_failures)}",
)
}
Ok(ValidateValidFuzzStats::{
profile: normalized,
seed,
attempts,
validated,
generator_config,
feature_stats,
feature_floors,
})
}
///|
priv struct GenValidBodySurfaceFacts {
has_block : Bool
has_loop : Bool
has_if : Bool
has_br : Bool
has_br_if : Bool
has_memory_op : Bool
has_table_op : Bool
}
///|
fn gen_valid_body_surface_facts_empty() -> GenValidBodySurfaceFacts {
{
has_block: false,
has_loop: false,
has_if: false,
has_br: false,
has_br_if: false,
has_memory_op: false,
has_table_op: false,
}
}
///|
fn gen_valid_body_surface_facts_merge(
left : GenValidBodySurfaceFacts,
right : GenValidBodySurfaceFacts,
) -> GenValidBodySurfaceFacts {
{
has_block: left.has_block || right.has_block,
has_loop: left.has_loop || right.has_loop,
has_if: left.has_if || right.has_if,
has_br: left.has_br || right.has_br,
has_br_if: left.has_br_if || right.has_br_if,
has_memory_op: left.has_memory_op || right.has_memory_op,
has_table_op: left.has_table_op || right.has_table_op,
}
}
///|
fn gen_valid_body_surface_facts_expr(expr : Expr) -> GenValidBodySurfaceFacts {
let Expr(instrs) = expr
let mut facts = gen_valid_body_surface_facts_empty()
for instr in instrs {
facts = gen_valid_body_surface_facts_merge(
facts,
gen_valid_body_surface_facts_instr(instr),
)
}
facts
}
///|
fn gen_valid_body_surface_facts_instr(
instr : Instruction,
) -> GenValidBodySurfaceFacts {
match instr {
Block(_, body) =>
GenValidBodySurfaceFacts::{
..gen_valid_body_surface_facts_expr(body),
has_block: true,
}
Loop(_, body) =>
GenValidBodySurfaceFacts::{
..gen_valid_body_surface_facts_expr(body),
has_loop: true,
}
If(_, then_body, else_body) => {
let mut facts = GenValidBodySurfaceFacts::{
..gen_valid_body_surface_facts_empty(),
has_if: true,
}
facts = gen_valid_body_surface_facts_merge(
facts,
gen_valid_body_surface_facts_expr(Expr::new(then_body)),
)
match else_body {
Some(branch) =>
facts = gen_valid_body_surface_facts_merge(
facts,
gen_valid_body_surface_facts_expr(Expr::new(branch)),
)
None => ()
}
facts
}
Br(_) =>
GenValidBodySurfaceFacts::{
..gen_valid_body_surface_facts_empty(),
has_br: true,
}
BrIf(_) =>
GenValidBodySurfaceFacts::{
..gen_valid_body_surface_facts_empty(),
has_br_if: true,
}
I32Load(_)
| I64Load(_)
| F32Load(_)
| F64Load(_)
| I32Load8S(_)
| I32Load8U(_)
| I32Load16S(_)
| I32Load16U(_)
| I64Load8S(_)
| I64Load8U(_)
| I64Load16S(_)
| I64Load16U(_)
| I64Load32S(_)
| I64Load32U(_)
| I32Store(_)
| I64Store(_)
| F32Store(_)
| F64Store(_)
| I32Store8(_)
| I32Store16(_)
| I64Store8(_)
| I64Store16(_)
| I64Store32(_)
| MemorySize(_)
| MemoryGrow(_)
| MemoryFill(_)
| MemoryCopy(_, _) =>
GenValidBodySurfaceFacts::{
..gen_valid_body_surface_facts_empty(),
has_memory_op: true,
}
TableGet(_)
| TableSet(_)
| TableSize(_)
| TableGrow(_)
| TableFill(_)
| TableCopy(_, _)
| TableInit(_, _) =>
GenValidBodySurfaceFacts::{
..gen_valid_body_surface_facts_empty(),
has_table_op: true,
}
_ => gen_valid_body_surface_facts_empty()
}
}
///|
fn gen_valid_body_surface_facts_module(
mod : Module,
) -> GenValidBodySurfaceFacts {
match mod.code_sec {
Some(CodeSec(funcs)) => {
let mut facts = gen_valid_body_surface_facts_empty()
for func in funcs {
let Func(_, body) = func
facts = gen_valid_body_surface_facts_merge(
facts,
gen_valid_body_surface_facts_expr(body),
)
}
facts
}
None => gen_valid_body_surface_facts_empty()
}
}
///|
priv struct GenValidCoverageSurfaceFacts {
import_funcs : Int32
import_tables : Int32
import_mems : Int32
import_globals : Int32
import_tags : Int32
passive_elems : Int32
declarative_elems : Int32
passive_datas : Int32
has_memory_init : Bool
has_data_drop : Bool
has_memory_copy : Bool
has_memory_fill : Bool
has_table_init : Bool
has_elem_drop : Bool
has_table_copy : Bool
has_table_fill : Bool
}
///|
fn gen_valid_coverage_surface_facts_empty() -> GenValidCoverageSurfaceFacts {
{
import_funcs: 0,
import_tables: 0,
import_mems: 0,
import_globals: 0,
import_tags: 0,
passive_elems: 0,
declarative_elems: 0,
passive_datas: 0,
has_memory_init: false,
has_data_drop: false,
has_memory_copy: false,
has_memory_fill: false,
has_table_init: false,
has_elem_drop: false,
has_table_copy: false,
has_table_fill: false,
}
}
///|
fn gen_valid_coverage_surface_facts_merge(
left : GenValidCoverageSurfaceFacts,
right : GenValidCoverageSurfaceFacts,
) -> GenValidCoverageSurfaceFacts {
{
import_funcs: left.import_funcs + right.import_funcs,
import_tables: left.import_tables + right.import_tables,
import_mems: left.import_mems + right.import_mems,
import_globals: left.import_globals + right.import_globals,
import_tags: left.import_tags + right.import_tags,
passive_elems: left.passive_elems + right.passive_elems,
declarative_elems: left.declarative_elems + right.declarative_elems,
passive_datas: left.passive_datas + right.passive_datas,
has_memory_init: left.has_memory_init || right.has_memory_init,
has_data_drop: left.has_data_drop || right.has_data_drop,
has_memory_copy: left.has_memory_copy || right.has_memory_copy,
has_memory_fill: left.has_memory_fill || right.has_memory_fill,
has_table_init: left.has_table_init || right.has_table_init,
has_elem_drop: left.has_elem_drop || right.has_elem_drop,
has_table_copy: left.has_table_copy || right.has_table_copy,
has_table_fill: left.has_table_fill || right.has_table_fill,
}
}
///|
fn gen_valid_coverage_surface_facts_instr(
instr : Instruction,
) -> GenValidCoverageSurfaceFacts {
match instr {
Block(_, body) | Loop(_, body) =>
gen_valid_coverage_surface_facts_expr(body)
If(_, then_body, else_body) => {
let mut facts = gen_valid_coverage_surface_facts_expr(
Expr::new(then_body),
)
match else_body {
Some(branch) =>
facts = gen_valid_coverage_surface_facts_merge(
facts,
gen_valid_coverage_surface_facts_expr(Expr::new(branch)),
)
None => ()
}
facts
}
MemoryInit(_, _) =>
{ ..gen_valid_coverage_surface_facts_empty(), has_memory_init: true, }
DataDrop(_) =>
{ ..gen_valid_coverage_surface_facts_empty(), has_data_drop: true, }
MemoryCopy(_, _) =>
{ ..gen_valid_coverage_surface_facts_empty(), has_memory_copy: true, }
MemoryFill(_) =>
{ ..gen_valid_coverage_surface_facts_empty(), has_memory_fill: true, }
TableInit(_, _) =>
{ ..gen_valid_coverage_surface_facts_empty(), has_table_init: true, }
ElemDrop(_) =>
{ ..gen_valid_coverage_surface_facts_empty(), has_elem_drop: true, }
TableCopy(_, _) =>
{ ..gen_valid_coverage_surface_facts_empty(), has_table_copy: true, }
TableFill(_) =>
{ ..gen_valid_coverage_surface_facts_empty(), has_table_fill: true, }
_ => gen_valid_coverage_surface_facts_empty()
}
}
///|
fn gen_valid_coverage_surface_facts_expr(
expr : Expr,
) -> GenValidCoverageSurfaceFacts {
let Expr(instrs) = expr
let mut facts = gen_valid_coverage_surface_facts_empty()
for instr in instrs {
facts = gen_valid_coverage_surface_facts_merge(
facts,
gen_valid_coverage_surface_facts_instr(instr),
)
}
facts
}
///|
fn gen_valid_coverage_surface_facts_module(
mod : Module,
) -> GenValidCoverageSurfaceFacts {
let mut facts = gen_valid_coverage_surface_facts_empty()
match mod.import_sec {
Some(ImportSec(imports)) =>
for imp in imports {
let Import(_, _, extern_type) = imp
facts = gen_valid_coverage_surface_facts_merge(
facts,
match extern_type {
FuncExternType(_) =>
{ ..gen_valid_coverage_surface_facts_empty(), import_funcs: 1, }
TableExternType(_) =>
{ ..gen_valid_coverage_surface_facts_empty(), import_tables: 1, }
MemExternType(_) =>
{ ..gen_valid_coverage_surface_facts_empty(), import_mems: 1, }
GlobalExternType(_) =>
{ ..gen_valid_coverage_surface_facts_empty(), import_globals: 1, }
TagExternType(_) =>
{ ..gen_valid_coverage_surface_facts_empty(), import_tags: 1, }
},
)
}
None => ()
}
match mod.elem_sec {
Some(ElemSec(elems)) =>
for elem in elems {
let Elem(mode, _) = elem
facts = gen_valid_coverage_surface_facts_merge(
facts,
match mode {
Passive =>
{ ..gen_valid_coverage_surface_facts_empty(), passive_elems: 1, }
Declarative =>
{
..gen_valid_coverage_surface_facts_empty(),
declarative_elems: 1,
}
Active(_, expr) => gen_valid_coverage_surface_facts_expr(expr)
},
)
}
None => ()
}
match mod.data_sec {
Some(DataSec(datas)) =>
for data in datas {
let Data(mode, _) = data
facts = gen_valid_coverage_surface_facts_merge(
facts,
match mode {
Passive =>
{ ..gen_valid_coverage_surface_facts_empty(), passive_datas: 1, }
Active(_, expr) => gen_valid_coverage_surface_facts_expr(expr)
},
)
}
None => ()
}
match mod.code_sec {
Some(CodeSec(funcs)) =>
for func in funcs {
let Func(_, body) = func
facts = gen_valid_coverage_surface_facts_merge(
facts,
gen_valid_coverage_surface_facts_expr(body),
)
}
None => ()
}
facts
}
///|
priv struct GenValidGcSurfaceFacts {
struct_types : Int32
array_types : Int32
descriptor_types : Int32
rec_groups : Int32
typed_ref_globals : Int32
typed_ref_tables : Int32
typed_ref_locals : Int32
exact_typed_ref_globals : Int32
exact_typed_ref_locals : Int32
exact_typed_ref_results : Int32
has_struct_new : Bool
has_struct_get : Bool
has_struct_set : Bool
has_array_new : Bool
has_array_new_data : Bool
has_array_new_elem : Bool
has_array_get : Bool
has_array_set : Bool
has_array_init_data : Bool
has_array_init_elem : Bool
has_array_len : Bool
has_string_const : Bool
has_string_new : Bool
has_string_encode : Bool
has_ref_get_desc : Bool
has_ref_test : Bool
has_ref_test_desc : Bool
has_ref_cast : Bool
has_ref_cast_desc_eq : Bool
has_ref_eq : Bool
has_ref_as_non_null : Bool
has_exact_ref_null : Bool
has_call_ref : Bool
has_br_on_null : Bool
has_br_on_non_null : Bool
has_br_on_cast : Bool
has_br_on_cast_fail : Bool
has_mixed_catch_try_table : Bool
has_branchy_mixed_catch_try_table : Bool
has_try_table : Bool
has_throw : Bool
has_throw_ref : Bool
}
///|
fn gen_valid_gc_surface_facts_empty() -> GenValidGcSurfaceFacts {
{
struct_types: 0,
array_types: 0,
descriptor_types: 0,
rec_groups: 0,
typed_ref_globals: 0,
typed_ref_tables: 0,
typed_ref_locals: 0,
exact_typed_ref_globals: 0,
exact_typed_ref_locals: 0,
exact_typed_ref_results: 0,
has_struct_new: false,
has_struct_get: false,
has_struct_set: false,
has_array_new: false,
has_array_new_data: false,
has_array_new_elem: false,
has_array_get: false,
has_array_set: false,
has_array_init_data: false,
has_array_init_elem: false,
has_array_len: false,
has_string_const: false,
has_string_new: false,
has_string_encode: false,
has_ref_get_desc: false,
has_ref_test: false,
has_ref_test_desc: false,
has_ref_cast: false,
has_ref_cast_desc_eq: false,
has_ref_eq: false,
has_ref_as_non_null: false,
has_exact_ref_null: false,
has_call_ref: false,
has_br_on_null: false,
has_br_on_non_null: false,
has_br_on_cast: false,
has_br_on_cast_fail: false,
has_mixed_catch_try_table: false,
has_branchy_mixed_catch_try_table: false,
has_try_table: false,
has_throw: false,
has_throw_ref: false,
}
}
///|
fn gen_valid_gc_surface_facts_merge(
left : GenValidGcSurfaceFacts,
right : GenValidGcSurfaceFacts,
) -> GenValidGcSurfaceFacts {
{
struct_types: left.struct_types + right.struct_types,
array_types: left.array_types + right.array_types,
descriptor_types: left.descriptor_types + right.descriptor_types,
rec_groups: left.rec_groups + right.rec_groups,
typed_ref_globals: left.typed_ref_globals + right.typed_ref_globals,
typed_ref_tables: left.typed_ref_tables + right.typed_ref_tables,
typed_ref_locals: left.typed_ref_locals + right.typed_ref_locals,
exact_typed_ref_globals: left.exact_typed_ref_globals +
right.exact_typed_ref_globals,
exact_typed_ref_locals: left.exact_typed_ref_locals +
right.exact_typed_ref_locals,
exact_typed_ref_results: left.exact_typed_ref_results +
right.exact_typed_ref_results,
has_struct_new: left.has_struct_new || right.has_struct_new,
has_struct_get: left.has_struct_get || right.has_struct_get,
has_struct_set: left.has_struct_set || right.has_struct_set,
has_array_new: left.has_array_new || right.has_array_new,
has_array_new_data: left.has_array_new_data || right.has_array_new_data,
has_array_new_elem: left.has_array_new_elem || right.has_array_new_elem,
has_array_get: left.has_array_get || right.has_array_get,
has_array_set: left.has_array_set || right.has_array_set,
has_array_init_data: left.has_array_init_data || right.has_array_init_data,
has_array_init_elem: left.has_array_init_elem || right.has_array_init_elem,
has_array_len: left.has_array_len || right.has_array_len,
has_string_const: left.has_string_const || right.has_string_const,
has_string_new: left.has_string_new || right.has_string_new,
has_string_encode: left.has_string_encode || right.has_string_encode,
has_ref_get_desc: left.has_ref_get_desc || right.has_ref_get_desc,
has_ref_test: left.has_ref_test || right.has_ref_test,
has_ref_test_desc: left.has_ref_test_desc || right.has_ref_test_desc,
has_ref_cast: left.has_ref_cast || right.has_ref_cast,
has_ref_cast_desc_eq: left.has_ref_cast_desc_eq ||
right.has_ref_cast_desc_eq,
has_ref_eq: left.has_ref_eq || right.has_ref_eq,
has_ref_as_non_null: left.has_ref_as_non_null || right.has_ref_as_non_null,
has_exact_ref_null: left.has_exact_ref_null || right.has_exact_ref_null,
has_call_ref: left.has_call_ref || right.has_call_ref,
has_br_on_null: left.has_br_on_null || right.has_br_on_null,
has_br_on_non_null: left.has_br_on_non_null || right.has_br_on_non_null,
has_br_on_cast: left.has_br_on_cast || right.has_br_on_cast,
has_br_on_cast_fail: left.has_br_on_cast_fail || right.has_br_on_cast_fail,
has_mixed_catch_try_table: left.has_mixed_catch_try_table ||
right.has_mixed_catch_try_table,
has_branchy_mixed_catch_try_table: left.has_branchy_mixed_catch_try_table ||
right.has_branchy_mixed_catch_try_table,
has_try_table: left.has_try_table || right.has_try_table,
has_throw: left.has_throw || right.has_throw,
has_throw_ref: left.has_throw_ref || right.has_throw_ref,
}
}
///|
fn gen_valid_gc_surface_ref_score(rt : RefType) -> Int32 {
match rt.get_heap_type() {
HeapType(_) => 1
AbsHeapTypeHeapType(abs) =>
if abs == AbsHeapType::any() ||
abs == AbsHeapType::eq() ||
abs == AbsHeapType::i31() ||
abs == AbsHeapType::struct_() ||
abs == AbsHeapType::array() ||
abs == AbsHeapType::string_() ||
abs == AbsHeapType::exn() {
1
} else {
0
}
_ => 0
}
}
///|
fn gen_valid_gc_surface_val_score(vt : ValType) -> Int32 {
match vt {
RefTypeValType(rt) => gen_valid_gc_surface_ref_score(rt)
_ => 0
}
}
///|
fn gen_valid_gc_surface_exact_val_score(vt : ValType) -> Int32 {
match vt {
RefTypeValType(rt) =>
if rt.is_exact() {
gen_valid_gc_surface_ref_score(rt)
} else {
0
}
_ => 0
}
}
///|
fn gen_valid_gc_surface_has_mixed_catches(catches : Array[Catch]) -> Bool {
let mut has_catch = false
let mut has_catch_ref = false
let mut has_catch_all = false
let mut has_catch_all_ref = false
for catch_ in catches {
match catch_ {
Catch(_, _) => has_catch = true
CatchRef(_, _) => has_catch_ref = true
CatchAll(_) => has_catch_all = true
CatchAllRef(_) => has_catch_all_ref = true
}
}
let mut kinds = 0
if has_catch {
kinds += 1
}
if has_catch_ref {
kinds += 1
}
if has_catch_all {
kinds += 1
}
if has_catch_all_ref {
kinds += 1
}
catches.length() > 1 && kinds > 1
}
///|
fn gen_valid_gc_surface_has_branchy_control(
facts : GenValidBodySurfaceFacts,
) -> Bool {
facts.has_block ||
facts.has_loop ||
facts.has_if ||
facts.has_br ||
facts.has_br_if
}
///|
fn gen_valid_gc_surface_facts_sub_type(
subtype : SubType,
) -> GenValidGcSurfaceFacts {
let descriptor_types = if subtype.descriptor_type() is Some(_) ||
subtype.describes_type() is Some(_) {
1
} else {
0
}
match subtype.get_comptype() {
StructCompType(_) =>
{
..gen_valid_gc_surface_facts_empty(),
struct_types: 1,
descriptor_types,
}
ArrayCompType(_) =>
{
..gen_valid_gc_surface_facts_empty(),
array_types: 1,
descriptor_types,
}
_ => { ..gen_valid_gc_surface_facts_empty(), descriptor_types, }
}
}
///|
fn gen_valid_gc_surface_facts_rec_type(
rec_type : RecType,
) -> GenValidGcSurfaceFacts {
match rec_type {
SingleRecType(subtype) => gen_valid_gc_surface_facts_sub_type(subtype)
GroupRecType(group) => {
let mut facts = { ..gen_valid_gc_surface_facts_empty(), rec_groups: 1, }
for subtype in group {
facts = gen_valid_gc_surface_facts_merge(
facts,
gen_valid_gc_surface_facts_sub_type(subtype),
)
}
facts
}
}
}
///|
fn gen_valid_gc_surface_facts_instr(
instr : Instruction,
) -> GenValidGcSurfaceFacts {
match instr {
Block(_, body) | Loop(_, body) => gen_valid_gc_surface_facts_expr(body)
If(_, then_body, else_body) => {
let mut facts = gen_valid_gc_surface_facts_expr(Expr::new(then_body))
match else_body {
Some(branch) =>
facts = gen_valid_gc_surface_facts_merge(
facts,
gen_valid_gc_surface_facts_expr(Expr::new(branch)),
)
None => ()
}
facts
}
TryTable(_, catches, body) => {
let body_facts = gen_valid_gc_surface_facts_expr(body)
let mixed_catches = gen_valid_gc_surface_has_mixed_catches(catches)
{
..body_facts,
has_mixed_catch_try_table: mixed_catches,
has_branchy_mixed_catch_try_table: mixed_catches &&
gen_valid_gc_surface_has_branchy_control(
gen_valid_body_surface_facts_expr(body),
),
has_try_table: true,
}
}
Throw(_) => { ..gen_valid_gc_surface_facts_empty(), has_throw: true, }
ThrowRef => { ..gen_valid_gc_surface_facts_empty(), has_throw_ref: true, }
StructNew(_)
| StructNewDefault(_)
| StructNewDesc(_)
| StructNewDefaultDesc(_) =>
{ ..gen_valid_gc_surface_facts_empty(), has_struct_new: true, }
StructGet(_, _)
| StructGetS(_, _)
| StructGetU(_, _)
| StructAtomicGet(_, _, _)
| StructAtomicGetS(_, _, _)
| StructAtomicGetU(_, _, _) =>
{ ..gen_valid_gc_surface_facts_empty(), has_struct_get: true, }
StructAtomicRmw(_, _, _, _) | StructAtomicCmpxchg(_, _, _) =>
{
..gen_valid_gc_surface_facts_empty(),
has_struct_get: true,
has_struct_set: true,
}
StructSet(_, _) | StructAtomicSet(_, _, _) =>
{ ..gen_valid_gc_surface_facts_empty(), has_struct_set: true, }
ArrayNew(_) | ArrayNewDefault(_) | ArrayNewFixed(_, _) =>
{ ..gen_valid_gc_surface_facts_empty(), has_array_new: true, }
ArrayNewData(_, _) =>
{
..gen_valid_gc_surface_facts_empty(),
has_array_new: true,
has_array_new_data: true,
}
ArrayNewElem(_, _) =>
{
..gen_valid_gc_surface_facts_empty(),
has_array_new: true,
has_array_new_elem: true,
}
StringNewUtf8Array
| StringNewWtf16Array
| StringNewLossyUtf8Array
| StringNewWtf8Array =>
{
..gen_valid_gc_surface_facts_empty(),
has_array_new: true,
has_string_new: true,
}
ArrayGet(_)
| ArrayGetS(_)
| ArrayGetU(_)
| ArrayAtomicGet(_, _)
| ArrayAtomicGetS(_, _)
| ArrayAtomicGetU(_, _) =>
{ ..gen_valid_gc_surface_facts_empty(), has_array_get: true, }
ArrayAtomicRmw(_, _, _) | ArrayAtomicCmpxchg(_, _) =>
{
..gen_valid_gc_surface_facts_empty(),
has_array_get: true,
has_array_set: true,
}
ArrayAtomicSet(_, _) | ArraySet(_) | ArrayFill(_) | ArrayCopy(_, _) =>
{ ..gen_valid_gc_surface_facts_empty(), has_array_set: true, }
ArrayInitData(_, _) =>
{
..gen_valid_gc_surface_facts_empty(),
has_array_set: true,
has_array_init_data: true,
}
ArrayInitElem(_, _) =>
{
..gen_valid_gc_surface_facts_empty(),
has_array_set: true,
has_array_init_elem: true,
}
StringEncodeUtf8Array
| StringEncodeWtf16Array
| StringEncodeLossyUtf8Array
| StringEncodeWtf8Array =>
{
..gen_valid_gc_surface_facts_empty(),
has_array_set: true,
has_string_encode: true,
}
ArrayLen => { ..gen_valid_gc_surface_facts_empty(), has_array_len: true, }
RefNull(rt) =>
{
..gen_valid_gc_surface_facts_empty(),
has_exact_ref_null: rt.is_exact(),
}
StringConst(_) =>
{ ..gen_valid_gc_surface_facts_empty(), has_string_const: true, }
RefGetDesc(_) =>
{ ..gen_valid_gc_surface_facts_empty(), has_ref_get_desc: true, }
RefTest(_, _, _) =>
{ ..gen_valid_gc_surface_facts_empty(), has_ref_test: true, }
RefTestDesc(_, _) =>
{
..gen_valid_gc_surface_facts_empty(),
has_ref_test: true,
has_ref_test_desc: true,
}
RefCast(_, _, _) =>
{ ..gen_valid_gc_surface_facts_empty(), has_ref_cast: true, }
RefCastDescEq(_, _, _) =>
{
..gen_valid_gc_surface_facts_empty(),
has_ref_cast: true,
has_ref_cast_desc_eq: true,
}
RefEq => { ..gen_valid_gc_surface_facts_empty(), has_ref_eq: true, }
RefAsNonNull =>
{ ..gen_valid_gc_surface_facts_empty(), has_ref_as_non_null: true, }
CallRef(_) | ReturnCallRef(_) =>
{ ..gen_valid_gc_surface_facts_empty(), has_call_ref: true, }
BrOnNull(_) =>
{ ..gen_valid_gc_surface_facts_empty(), has_br_on_null: true, }
BrOnNonNull(_) =>
{ ..gen_valid_gc_surface_facts_empty(), has_br_on_non_null: true, }
BrOnCast(_, _, _, _) =>
{ ..gen_valid_gc_surface_facts_empty(), has_br_on_cast: true, }
BrOnCastFail(_, _, _, _) =>
{ ..gen_valid_gc_surface_facts_empty(), has_br_on_cast_fail: true, }
_ => gen_valid_gc_surface_facts_empty()
}
}
///|
fn gen_valid_gc_surface_facts_expr(expr : Expr) -> GenValidGcSurfaceFacts {
let Expr(instrs) = expr
let mut facts = gen_valid_gc_surface_facts_empty()
for instr in instrs {
facts = gen_valid_gc_surface_facts_merge(
facts,
gen_valid_gc_surface_facts_instr(instr),
)
}
facts
}
///|
fn gen_valid_gc_surface_facts_module(mod : Module) -> GenValidGcSurfaceFacts {
let mut facts = gen_valid_gc_surface_facts_empty()
match mod.type_sec {
Some(TypeSec(type_defs)) =>
for rec_type in type_defs {
facts = gen_valid_gc_surface_facts_merge(
facts,
gen_valid_gc_surface_facts_rec_type(rec_type),
)
match rec_type {
SingleRecType(subtype) =>
match subtype.get_comptype() {
FuncCompType(_, results) =>
for vt in results {
facts = gen_valid_gc_surface_facts_merge(facts, {
..gen_valid_gc_surface_facts_empty(),
exact_typed_ref_results: gen_valid_gc_surface_exact_val_score(
vt,
),
})
}
_ => ()
}
GroupRecType(group) =>
for subtype in group {
match subtype.get_comptype() {
FuncCompType(_, results) =>
for vt in results {
facts = gen_valid_gc_surface_facts_merge(facts, {
..gen_valid_gc_surface_facts_empty(),
exact_typed_ref_results: gen_valid_gc_surface_exact_val_score(
vt,
),
})
}
_ => ()
}
}
}
}
None => ()
}
match mod.import_sec {
Some(ImportSec(imports)) =>
for imp in imports {
let Import(_, _, extern_type) = imp
facts = gen_valid_gc_surface_facts_merge(
facts,
match extern_type {
TableExternType(TableType(rt, _)) =>
{
..gen_valid_gc_surface_facts_empty(),
typed_ref_tables: gen_valid_gc_surface_ref_score(rt),
}
GlobalExternType(GlobalType(vt, _)) =>
{
..gen_valid_gc_surface_facts_empty(),
typed_ref_globals: gen_valid_gc_surface_val_score(vt),
exact_typed_ref_globals: gen_valid_gc_surface_exact_val_score(
vt,
),
}
_ => gen_valid_gc_surface_facts_empty()
},
)
}
None => ()
}
match mod.table_sec {
Some(TableSec(tables)) =>
for table in tables {
let Table(TableType(rt, _), _) = table
facts = gen_valid_gc_surface_facts_merge(facts, {
..gen_valid_gc_surface_facts_empty(),
typed_ref_tables: gen_valid_gc_surface_ref_score(rt),
})
}
None => ()
}
match mod.global_sec {
Some(GlobalSec(globals)) =>
for global in globals {
let Global(GlobalType(vt, _), _) = global
facts = gen_valid_gc_surface_facts_merge(facts, {
..gen_valid_gc_surface_facts_empty(),
typed_ref_globals: gen_valid_gc_surface_val_score(vt),
exact_typed_ref_globals: gen_valid_gc_surface_exact_val_score(vt),
})
}
None => ()
}
match mod.code_sec {
Some(CodeSec(funcs)) =>
for func in funcs {
let Func(locals, body) = func
for run in locals.runs() {
let { count, vt, } = run
ignore(count)
facts = gen_valid_gc_surface_facts_merge(facts, {
..gen_valid_gc_surface_facts_empty(),
typed_ref_locals: gen_valid_gc_surface_val_score(vt),
exact_typed_ref_locals: gen_valid_gc_surface_exact_val_score(vt),
})
}
facts = gen_valid_gc_surface_facts_merge(
facts,
gen_valid_gc_surface_facts_expr(body),
)
}
None => ()
}
facts
}
///|
test "validate_valid_run_config returns profile attempts and generator config" {
let smoke = match validate_valid_run_config("smoke") {
Ok(v) => v
Err(msg) => fail(msg)
}
let ci = match validate_valid_run_config("ci") {
Ok(v) => v
Err(msg) => fail(msg)
}
let stress = match validate_valid_run_config("stress") {
Ok(v) => v
Err(msg) => fail(msg)
}
@debug.assert_eq(smoke.profile, "smoke")
@debug.assert_eq(smoke.attempts, 128)
@debug.assert_eq(ci.attempts, 5000)
@debug.assert_eq(stress.attempts, 100000)
@debug.assert_eq(
smoke.generator_config,
GenValidConfig::for_profile(CoverageForcedAllFeaturesProfile),
)
assert_true(
ci.generator_config.max_defined_funcs >=
smoke.generator_config.max_defined_funcs,
)
assert_true(
stress.generator_config.max_body_statements >=
ci.generator_config.max_body_statements,
)
assert_true(smoke.feature_floors.length() > 0)
assert_true(ci.feature_floors.length() >= smoke.feature_floors.length())
assert_true(stress.feature_floors.length() >= ci.feature_floors.length())
}
///|
fn validate_valid_test_floor_min(
floors : Array[ValidateValidFeatureFloor],
key : ValidateValidFeatureKey,
) -> Int32 {
for floor in floors {
if floor.key == key {
return floor.min_count
}
}
0
}
///|
test "validate_valid_run_config retunes FZG feature floors by profile" {
let smoke = match validate_valid_run_config("smoke") {
Ok(v) => v
Err(msg) => fail(msg)
}
let ci = match validate_valid_run_config("ci") {
Ok(v) => v
Err(msg) => fail(msg)
}
let stress = match validate_valid_run_config("stress") {
Ok(v) => v
Err(msg) => fail(msg)
}
for floor in validate_valid_fzg_feature_floors(1) {
@debug.assert_eq(
validate_valid_test_floor_min(smoke.feature_floors, floor.key),
1,
)
@debug.assert_eq(
validate_valid_test_floor_min(ci.feature_floors, floor.key),
10,
)
@debug.assert_eq(
validate_valid_test_floor_min(stress.feature_floors, floor.key),
100,
)
}
}
///|
test "check_validate_valid_feature_floors reports unmet minimums" {
let floors = [
{ key: Imports, min_count: 2, },
{ key: FuncExports, min_count: 4, },
{ key: Calls, min_count: 1, },
{ key: BranchHeavyControl, min_count: 3, },
]
let stats = {
..GenValidFeatureStats::empty(Natural),
modules: 4,
imports: 1,
func_exports: 4,
calls: 1,
branch_heavy_control: 0,
}
@debug.assert_eq(check_validate_valid_feature_floors(stats, floors), [
{ key: Imports, min_count: 2, actual_count: 1, },
{ key: BranchHeavyControl, min_count: 3, actual_count: 0, },
])
}
///|
test "validate_valid_feature_ledger reports optional missing FZG surfaces" {
let stats = {
..GenValidFeatureStats::empty(Natural),
modules: 2,
imports: 1,
}
let ledger = validate_valid_feature_ledger(stats, [])
let mut saw_imports = false
let mut saw_numeric_full_ops = false
let mut saw_name_custom_sections = false
for entry in ledger {
if entry.key == Imports {
saw_imports = true
@debug.assert_eq(entry.actual_count, 1)
@debug.assert_eq(entry.required_min, 0)
@debug.assert_eq(entry.status, Covered)
}
if entry.key == NumericFullOps {
saw_numeric_full_ops = true
@debug.assert_eq(entry.actual_count, 0)
@debug.assert_eq(entry.required_min, 0)
@debug.assert_eq(entry.status, MissingOptional)
}
if entry.key == NameCustomSections {
saw_name_custom_sections = true
@debug.assert_eq(entry.actual_count, 0)
@debug.assert_eq(entry.required_min, 0)
@debug.assert_eq(entry.status, MissingOptional)
}
}
assert_true(saw_imports)
assert_true(saw_numeric_full_ops)
assert_true(saw_name_custom_sections)
}
///|
test "check_validate_valid_feature_floors fails future FZG surfaces only when required" {
let stats = GenValidFeatureStats::empty(Natural)
@debug.assert_eq(check_validate_valid_feature_floors(stats, []), [])
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: NumericFullOps, min_count: 1, },
]),
[{ key: NumericFullOps, min_count: 1, actual_count: 0, }],
)
}
///|
test "gen_valid coverage-forced emits expanded scalar numeric surface" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26002UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(validate_valid_feature_actual_count(stats, NumericFullOps) > 0)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: NumericFullOps, min_count: 1, },
]),
[],
)
}
///|
test "gen_valid coverage-forced emits core control surface" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26003UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(validate_valid_feature_actual_count(stats, BrTable) > 0)
assert_true(
validate_valid_feature_actual_count(stats, StandaloneUnreachable) > 0,
)
assert_true(validate_valid_feature_actual_count(stats, LocalTee) > 0)
assert_true(validate_valid_feature_actual_count(stats, TypedSelect) > 0)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: BrTable, min_count: 1, },
{ key: StandaloneUnreachable, min_count: 1, },
{ key: LocalTee, min_count: 1, },
{ key: TypedSelect, min_count: 1, },
]),
[],
)
}
///|
fn validate_test_tail_call_forms_merge(
left : (Bool, Bool, Bool),
right : (Bool, Bool, Bool),
) -> (Bool, Bool, Bool) {
let (left_direct, left_indirect, left_ref) = left
let (right_direct, right_indirect, right_ref) = right
(
left_direct || right_direct,
left_indirect || right_indirect,
left_ref || right_ref,
)
}
///|
fn validate_test_tail_call_forms_expr(expr : Expr) -> (Bool, Bool, Bool) {
let Expr(instrs) = expr
let mut forms = (false, false, false)
for instr in instrs {
forms = validate_test_tail_call_forms_merge(
forms,
validate_test_tail_call_forms_instr(instr),
)
}
forms
}
///|
fn validate_test_tail_call_forms_instr(
instr : Instruction,
) -> (Bool, Bool, Bool) {
match instr {
ReturnCall(_) => (true, false, false)
ReturnCallIndirect(_, _) => (false, true, false)
ReturnCallRef(_) => (false, false, true)
Block(_, body) | Loop(_, body) => validate_test_tail_call_forms_expr(body)
If(_, then_body, else_body) => {
let mut forms = validate_test_tail_call_forms_expr(Expr::new(then_body))
match else_body {
Some(branch) =>
forms = validate_test_tail_call_forms_merge(
forms,
validate_test_tail_call_forms_expr(Expr::new(branch)),
)
None => ()
}
forms
}
TryTable(_, _, body) => validate_test_tail_call_forms_expr(body)
_ => (false, false, false)
}
}
///|
fn validate_test_tail_call_forms_module(mod : Module) -> (Bool, Bool, Bool) {
let mut forms = (false, false, false)
match mod.code_sec {
Some(CodeSec(funcs)) =>
for func in funcs {
let Func(_, body) = func
forms = validate_test_tail_call_forms_merge(
forms,
validate_test_tail_call_forms_expr(body),
)
}
None => ()
}
forms
}
///|
test "gen_valid coverage-forced emits tail-call surface" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26004UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let (has_return_call, has_return_call_indirect, has_return_call_ref) = validate_test_tail_call_forms_module(
mod,
)
assert_true(has_return_call)
assert_true(has_return_call_indirect)
assert_true(has_return_call_ref)
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(validate_valid_feature_actual_count(stats, TailCalls) > 0)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: TailCalls, min_count: 1, },
]),
[],
)
}
///|
priv struct ValidateTestMemoryForms {
i32_load : Bool
i64_load : Bool
f32_load : Bool
f64_load : Bool
i32_load8s : Bool
i32_load8u : Bool
i32_load16s : Bool
i32_load16u : Bool
i64_load8s : Bool
i64_load8u : Bool
i64_load16s : Bool
i64_load16u : Bool
i64_load32s : Bool
i64_load32u : Bool
i32_store : Bool
i64_store : Bool
f32_store : Bool
f64_store : Bool
i32_store8 : Bool
i32_store16 : Bool
i64_store8 : Bool
i64_store16 : Bool
i64_store32 : Bool
nonzero_memarg : Bool
}
///|
fn validate_test_memory_forms_empty() -> ValidateTestMemoryForms {
{
i32_load: false,
i64_load: false,
f32_load: false,
f64_load: false,
i32_load8s: false,
i32_load8u: false,
i32_load16s: false,
i32_load16u: false,
i64_load8s: false,
i64_load8u: false,
i64_load16s: false,
i64_load16u: false,
i64_load32s: false,
i64_load32u: false,
i32_store: false,
i64_store: false,
f32_store: false,
f64_store: false,
i32_store8: false,
i32_store16: false,
i64_store8: false,
i64_store16: false,
i64_store32: false,
nonzero_memarg: false,
}
}
///|
fn validate_test_memory_forms_merge(
left : ValidateTestMemoryForms,
right : ValidateTestMemoryForms,
) -> ValidateTestMemoryForms {
{
i32_load: left.i32_load || right.i32_load,
i64_load: left.i64_load || right.i64_load,
f32_load: left.f32_load || right.f32_load,
f64_load: left.f64_load || right.f64_load,
i32_load8s: left.i32_load8s || right.i32_load8s,
i32_load8u: left.i32_load8u || right.i32_load8u,
i32_load16s: left.i32_load16s || right.i32_load16s,
i32_load16u: left.i32_load16u || right.i32_load16u,
i64_load8s: left.i64_load8s || right.i64_load8s,
i64_load8u: left.i64_load8u || right.i64_load8u,
i64_load16s: left.i64_load16s || right.i64_load16s,
i64_load16u: left.i64_load16u || right.i64_load16u,
i64_load32s: left.i64_load32s || right.i64_load32s,
i64_load32u: left.i64_load32u || right.i64_load32u,
i32_store: left.i32_store || right.i32_store,
i64_store: left.i64_store || right.i64_store,
f32_store: left.f32_store || right.f32_store,
f64_store: left.f64_store || right.f64_store,
i32_store8: left.i32_store8 || right.i32_store8,
i32_store16: left.i32_store16 || right.i32_store16,
i64_store8: left.i64_store8 || right.i64_store8,
i64_store16: left.i64_store16 || right.i64_store16,
i64_store32: left.i64_store32 || right.i64_store32,
nonzero_memarg: left.nonzero_memarg || right.nonzero_memarg,
}
}
///|
fn validate_test_memarg_nonzero(ma : MemArg) -> Bool {
let MemArg(U32(align_pow), _, U64(offset)) = ma
align_pow.reinterpret_as_int() > 0 || offset > 0
}
///|
fn validate_test_memory_forms_expr(expr : Expr) -> ValidateTestMemoryForms {
let Expr(instrs) = expr
let mut forms = validate_test_memory_forms_empty()
for instr in instrs {
forms = validate_test_memory_forms_merge(
forms,
validate_test_memory_forms_instr(instr),
)
}
forms
}
///|
fn validate_test_memory_forms_instr(
instr : Instruction,
) -> ValidateTestMemoryForms {
match instr {
I32Load(ma) =>
{
..validate_test_memory_forms_empty(),
i32_load: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I64Load(ma) =>
{
..validate_test_memory_forms_empty(),
i64_load: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
F32Load(ma) =>
{
..validate_test_memory_forms_empty(),
f32_load: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
F64Load(ma) =>
{
..validate_test_memory_forms_empty(),
f64_load: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I32Load8S(ma) =>
{
..validate_test_memory_forms_empty(),
i32_load8s: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I32Load8U(ma) =>
{
..validate_test_memory_forms_empty(),
i32_load8u: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I32Load16S(ma) =>
{
..validate_test_memory_forms_empty(),
i32_load16s: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I32Load16U(ma) =>
{
..validate_test_memory_forms_empty(),
i32_load16u: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I64Load8S(ma) =>
{
..validate_test_memory_forms_empty(),
i64_load8s: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I64Load8U(ma) =>
{
..validate_test_memory_forms_empty(),
i64_load8u: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I64Load16S(ma) =>
{
..validate_test_memory_forms_empty(),
i64_load16s: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I64Load16U(ma) =>
{
..validate_test_memory_forms_empty(),
i64_load16u: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I64Load32S(ma) =>
{
..validate_test_memory_forms_empty(),
i64_load32s: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I64Load32U(ma) =>
{
..validate_test_memory_forms_empty(),
i64_load32u: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I32Store(ma) =>
{
..validate_test_memory_forms_empty(),
i32_store: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I64Store(ma) =>
{
..validate_test_memory_forms_empty(),
i64_store: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
F32Store(ma) =>
{
..validate_test_memory_forms_empty(),
f32_store: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
F64Store(ma) =>
{
..validate_test_memory_forms_empty(),
f64_store: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I32Store8(ma) =>
{
..validate_test_memory_forms_empty(),
i32_store8: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I32Store16(ma) =>
{
..validate_test_memory_forms_empty(),
i32_store16: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I64Store8(ma) =>
{
..validate_test_memory_forms_empty(),
i64_store8: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I64Store16(ma) =>
{
..validate_test_memory_forms_empty(),
i64_store16: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
I64Store32(ma) =>
{
..validate_test_memory_forms_empty(),
i64_store32: true,
nonzero_memarg: validate_test_memarg_nonzero(ma),
}
Block(_, body) | Loop(_, body) => validate_test_memory_forms_expr(body)
If(_, then_body, else_body) => {
let mut forms = validate_test_memory_forms_expr(Expr::new(then_body))
match else_body {
Some(branch) =>
forms = validate_test_memory_forms_merge(
forms,
validate_test_memory_forms_expr(Expr::new(branch)),
)
None => ()
}
forms
}
TryTable(_, _, body) => validate_test_memory_forms_expr(body)
_ => validate_test_memory_forms_empty()
}
}
///|
fn validate_test_memory_forms_module(mod : Module) -> ValidateTestMemoryForms {
let mut forms = validate_test_memory_forms_empty()
match mod.code_sec {
Some(CodeSec(funcs)) =>
for func in funcs {
let Func(_, body) = func
forms = validate_test_memory_forms_merge(
forms,
validate_test_memory_forms_expr(body),
)
}
None => ()
}
forms
}
///|
test "gen_valid coverage-forced emits scalar memory widths and memarg variation" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26005UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let forms = validate_test_memory_forms_module(mod)
assert_true(forms.i32_load)
assert_true(forms.i64_load)
assert_true(forms.f32_load)
assert_true(forms.f64_load)
assert_true(forms.i32_load8s)
assert_true(forms.i32_load8u)
assert_true(forms.i32_load16s)
assert_true(forms.i32_load16u)
assert_true(forms.i64_load8s)
assert_true(forms.i64_load8u)
assert_true(forms.i64_load16s)
assert_true(forms.i64_load16u)
assert_true(forms.i64_load32s)
assert_true(forms.i64_load32u)
assert_true(forms.i32_store)
assert_true(forms.i64_store)
assert_true(forms.f32_store)
assert_true(forms.f64_store)
assert_true(forms.i32_store8)
assert_true(forms.i32_store16)
assert_true(forms.i64_store8)
assert_true(forms.i64_store16)
assert_true(forms.i64_store32)
assert_true(forms.nonzero_memarg)
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(
validate_valid_feature_actual_count(stats, ScalarMemoryWidths) > 0,
)
assert_true(validate_valid_feature_actual_count(stats, NonzeroMemarg) > 0)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: ScalarMemoryWidths, min_count: 1, },
{ key: NonzeroMemarg, min_count: 1, },
]),
[],
)
}
///|
priv struct ValidateTestMemoryLimitForms {
zero_min : Bool
unbounded : Bool
shared : Bool
memory64 : Bool
shared_without_max : Bool
}
///|
fn validate_test_memory_limit_forms_empty() -> ValidateTestMemoryLimitForms {
{
zero_min: false,
unbounded: false,
shared: false,
memory64: false,
shared_without_max: false,
}
}
///|
fn validate_test_memory_limit_forms_mem(
forms : ValidateTestMemoryLimitForms,
mt : MemType,
) -> ValidateTestMemoryLimitForms {
let MemType(lim, shared) = mt
let (zero_min, unbounded, memory64) = match lim {
I32Limits(min, max) => (min == 0, max is None, false)
I64Limits(min, max) => (min == 0UL, max is None, true)
}
{
zero_min: forms.zero_min || zero_min,
unbounded: forms.unbounded || unbounded,
shared: forms.shared || shared,
memory64: forms.memory64 || memory64,
shared_without_max: forms.shared_without_max || (shared && unbounded),
}
}
///|
fn validate_test_memory_limit_forms_module(
mod : Module,
) -> ValidateTestMemoryLimitForms {
let mut forms = validate_test_memory_limit_forms_empty()
match mod.import_sec {
Some(ImportSec(imports)) =>
for imp in imports {
let Import(_, _, extern_type) = imp
match extern_type {
MemExternType(mt) =>
forms = validate_test_memory_limit_forms_mem(forms, mt)
_ => ()
}
}
None => ()
}
match mod.mem_sec {
Some(MemSec(mems)) =>
for mt in mems {
forms = validate_test_memory_limit_forms_mem(forms, mt)
}
None => ()
}
forms
}
///|
test "gen_valid coverage-forced emits memory limit and proposal variants" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26006UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let forms = validate_test_memory_limit_forms_module(mod)
assert_true(forms.zero_min)
assert_true(forms.unbounded)
assert_true(forms.shared)
assert_true(forms.memory64)
assert_false(forms.shared_without_max)
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(
validate_valid_feature_actual_count(stats, MemoryLimitVariants) > 0,
)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: MemoryLimitVariants, min_count: 1, },
]),
[],
)
}
///|
test "gen_valid memory limit variants obey proposal toggles" {
let base = GenValidConfig::coverage_forced_default()
let config = GenValidConfig::{
..base,
features: GenValidFeatureToggles::{
..base.features,
allow_memory_limit_variants: false,
allow_shared_memories: false,
allow_memory64: false,
},
}
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26007UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected toggled gen-valid module to validate, got \{msg}")
}
let forms = validate_test_memory_limit_forms_module(mod)
assert_false(forms.zero_min)
assert_false(forms.unbounded)
assert_false(forms.shared)
assert_false(forms.memory64)
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
@debug.assert_eq(
validate_valid_feature_actual_count(stats, MemoryLimitVariants),
0,
)
}
///|
priv struct ValidateTestTableLimitForms {
zero_min : Bool
unbounded : Bool
externref_table : Bool
gc_table : Bool
init_expr : Bool
nonzero_active_elem_table : Bool
}
///|
fn validate_test_table_limit_forms_empty() -> ValidateTestTableLimitForms {
{
zero_min: false,
unbounded: false,
externref_table: false,
gc_table: false,
init_expr: false,
nonzero_active_elem_table: false,
}
}
///|
fn validate_test_table_limit_forms_ref(rt : RefType) -> (Bool, Bool) {
let vt = ValType::ref_type(rt)
(
vt == ValType::externref(),
vt != ValType::funcref() && vt != ValType::externref(),
)
}
///|
fn validate_test_table_limit_forms_table(
forms : ValidateTestTableLimitForms,
table : Table,
) -> ValidateTestTableLimitForms {
let Table(TableType(rt, lim), init_opt) = table
let (zero_min, unbounded) = match lim {
I32Limits(min, max) => (min == 0U, max is None)
I64Limits(min, max) => (min == 0UL, max is None)
}
let (externref_table, gc_table) = validate_test_table_limit_forms_ref(rt)
{
zero_min: forms.zero_min || zero_min,
unbounded: forms.unbounded || unbounded,
externref_table: forms.externref_table || externref_table,
gc_table: forms.gc_table || gc_table,
init_expr: forms.init_expr || init_opt is Some(_),
nonzero_active_elem_table: forms.nonzero_active_elem_table,
}
}
///|
fn validate_test_table_limit_forms_module(
mod : Module,
) -> ValidateTestTableLimitForms {
let mut forms = validate_test_table_limit_forms_empty()
match mod.import_sec {
Some(ImportSec(imports)) =>
for imp in imports {
let Import(_, _, extern_type) = imp
match extern_type {
TableExternType(table_type) =>
forms = validate_test_table_limit_forms_table(
forms,
Table::new(table_type, None),
)
_ => ()
}
}
None => ()
}
match mod.table_sec {
Some(TableSec(tables)) =>
for table in tables {
forms = validate_test_table_limit_forms_table(forms, table)
}
None => ()
}
match mod.elem_sec {
Some(ElemSec(elems)) =>
for elem in elems {
let Elem(mode, _) = elem
match mode {
Active(TableIdx(table_idx), _) if table_idx.reinterpret_as_int() > 0 =>
forms = { ..forms, nonzero_active_elem_table: true, }
_ => ()
}
}
None => ()
}
forms
}
///|
test "gen_valid coverage-forced emits table limit and initializer variants" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26008UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let forms = validate_test_table_limit_forms_module(mod)
assert_true(forms.zero_min)
assert_true(forms.unbounded)
assert_true(forms.externref_table)
assert_true(forms.gc_table)
assert_true(forms.init_expr)
assert_true(forms.nonzero_active_elem_table)
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(
validate_valid_feature_actual_count(stats, TableLimitVariants) > 0,
)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: TableLimitVariants, min_count: 1, },
]),
[],
)
}
///|
test "gen_valid table limit variants obey toggle" {
let base = GenValidConfig::coverage_forced_default()
let config = GenValidConfig::{
..base,
features: GenValidFeatureToggles::{
..base.features,
allow_table_limit_variants: false,
},
}
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26009UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected toggled gen-valid module to validate, got \{msg}")
}
let forms = validate_test_table_limit_forms_module(mod)
assert_false(forms.zero_min)
assert_false(forms.unbounded)
assert_false(forms.init_expr)
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
@debug.assert_eq(
validate_valid_feature_actual_count(stats, TableLimitVariants),
0,
)
}
///|
priv struct ValidateTestConstExprForms {
numeric_const_in_global : Bool
global_get_in_global : Bool
imported_global_get_in_global : Bool
prior_global_get_in_global : Bool
ref_func_in_global : Bool
ref_null_in_global : Bool
gc_ref_in_global : Bool
global_get_in_table_init : Bool
ref_func_in_table_init : Bool
ref_null_in_table_init : Bool
global_get_in_elem_offset : Bool
imported_global_get_in_elem_offset : Bool
numeric_const_in_elem_offset : Bool
global_get_in_data_offset : Bool
imported_global_get_in_data_offset : Bool
numeric_const_in_data_offset : Bool
}
///|
fn validate_test_const_expr_forms_empty() -> ValidateTestConstExprForms {
{
numeric_const_in_global: false,
global_get_in_global: false,
imported_global_get_in_global: false,
prior_global_get_in_global: false,
ref_func_in_global: false,
ref_null_in_global: false,
gc_ref_in_global: false,
global_get_in_table_init: false,
ref_func_in_table_init: false,
ref_null_in_table_init: false,
global_get_in_elem_offset: false,
imported_global_get_in_elem_offset: false,
numeric_const_in_elem_offset: false,
global_get_in_data_offset: false,
imported_global_get_in_data_offset: false,
numeric_const_in_data_offset: false,
}
}
///|
fn validate_test_expr_has_numeric_const(expr : Expr) -> Bool {
let Expr(instrs) = expr
for instr in instrs {
match instr {
I32Const(_) | I64Const(_) | F32Const(_) | F64Const(_) => return true
_ => ()
}
}
false
}
///|
fn validate_test_expr_has_global_get(expr : Expr) -> Bool {
let Expr(instrs) = expr
for instr in instrs {
match instr {
GlobalGet(_) => return true
_ => ()
}
}
false
}
///|
fn validate_test_expr_has_ref_func(expr : Expr) -> Bool {
let Expr(instrs) = expr
for instr in instrs {
match instr {
RefFunc(_) => return true
_ => ()
}
}
false
}
///|
fn validate_test_expr_has_ref_null(expr : Expr) -> Bool {
let Expr(instrs) = expr
for instr in instrs {
match instr {
RefNull(_) => return true
_ => ()
}
}
false
}
///|
fn validate_test_expr_has_gc_const(expr : Expr) -> Bool {
let Expr(instrs) = expr
for instr in instrs {
match instr {
StructNew(_)
| StructNewDefault(_)
| StructNewDesc(_)
| StructNewDefaultDesc(_) => return true
_ => ()
}
}
false
}
///|
fn validate_test_imported_global_count(mod : Module) -> Int32 {
match mod.import_sec {
Some(ImportSec(imports)) => {
let mut count = 0
for imp in imports {
let Import(_, _, extern_type) = imp
match extern_type {
GlobalExternType(_) => count += 1
_ => ()
}
}
count
}
None => 0
}
}
///|
fn validate_test_expr_has_imported_global_get(
expr : Expr,
imported_global_count : Int32,
) -> Bool {
let Expr(instrs) = expr
for instr in instrs {
match instr {
GlobalGet(idx) => {
let GlobalIdx(idx_u) = idx
if idx_u.reinterpret_as_int() < imported_global_count {
return true
}
}
_ => ()
}
}
false
}
///|
fn validate_test_expr_has_prior_global_get(
expr : Expr,
upper_bound : Int32,
) -> Bool {
let Expr(instrs) = expr
for instr in instrs {
match instr {
GlobalGet(idx) => {
let GlobalIdx(idx_u) = idx
if idx_u.reinterpret_as_int() < upper_bound {
return true
}
}
_ => ()
}
}
false
}
///|
fn validate_test_const_expr_forms_module(
mod : Module,
) -> ValidateTestConstExprForms {
let mut forms = validate_test_const_expr_forms_empty()
let imported_global_count = validate_test_imported_global_count(mod)
match mod.global_sec {
Some(GlobalSec(globals)) =>
for i = 0; i < globals.length(); i = i + 1 {
let Global(_, init) = globals[i]
forms = {
..forms,
numeric_const_in_global: forms.numeric_const_in_global ||
validate_test_expr_has_numeric_const(init),
global_get_in_global: forms.global_get_in_global ||
validate_test_expr_has_global_get(init),
imported_global_get_in_global: forms.imported_global_get_in_global ||
validate_test_expr_has_imported_global_get(
init, imported_global_count,
),
prior_global_get_in_global: forms.prior_global_get_in_global ||
validate_test_expr_has_prior_global_get(
init,
imported_global_count + i,
),
ref_func_in_global: forms.ref_func_in_global ||
validate_test_expr_has_ref_func(init),
ref_null_in_global: forms.ref_null_in_global ||
validate_test_expr_has_ref_null(init),
gc_ref_in_global: forms.gc_ref_in_global ||
validate_test_expr_has_gc_const(init),
}
}
None => ()
}
match mod.table_sec {
Some(TableSec(tables)) =>
for table in tables {
let Table(_, init_opt) = table
match init_opt {
Some(init) =>
forms = {
..forms,
global_get_in_table_init: forms.global_get_in_table_init ||
validate_test_expr_has_global_get(init),
ref_func_in_table_init: forms.ref_func_in_table_init ||
validate_test_expr_has_ref_func(init),
ref_null_in_table_init: forms.ref_null_in_table_init ||
validate_test_expr_has_ref_null(init),
}
None => ()
}
}
None => ()
}
match mod.elem_sec {
Some(ElemSec(elems)) =>
for elem in elems {
let Elem(mode, _) = elem
match mode {
Active(_, offset) =>
forms = {
..forms,
numeric_const_in_elem_offset: forms.numeric_const_in_elem_offset ||
validate_test_expr_has_numeric_const(offset),
global_get_in_elem_offset: forms.global_get_in_elem_offset ||
validate_test_expr_has_global_get(offset),
imported_global_get_in_elem_offset: forms.imported_global_get_in_elem_offset ||
validate_test_expr_has_imported_global_get(
offset, imported_global_count,
),
}
_ => ()
}
}
None => ()
}
match mod.data_sec {
Some(DataSec(datas)) =>
for data in datas {
let Data(mode, _) = data
match mode {
Active(_, offset) =>
forms = {
..forms,
numeric_const_in_data_offset: forms.numeric_const_in_data_offset ||
validate_test_expr_has_numeric_const(offset),
global_get_in_data_offset: forms.global_get_in_data_offset ||
validate_test_expr_has_global_get(offset),
imported_global_get_in_data_offset: forms.imported_global_get_in_data_offset ||
validate_test_expr_has_imported_global_get(
offset, imported_global_count,
),
}
Passive => ()
}
}
None => ()
}
forms
}
///|
test "gen_valid coverage-forced emits const-expression variants" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf2600aUL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let forms = validate_test_const_expr_forms_module(mod)
assert_true(forms.numeric_const_in_global)
assert_true(forms.global_get_in_global)
assert_true(forms.imported_global_get_in_global)
assert_true(forms.prior_global_get_in_global)
assert_true(forms.ref_func_in_global)
assert_true(forms.ref_null_in_global)
assert_true(forms.gc_ref_in_global)
assert_true(forms.global_get_in_table_init)
assert_true(forms.ref_func_in_table_init)
assert_true(forms.ref_null_in_table_init)
assert_true(forms.global_get_in_elem_offset)
assert_true(forms.imported_global_get_in_elem_offset)
assert_true(forms.numeric_const_in_elem_offset)
assert_true(forms.global_get_in_data_offset)
assert_true(forms.imported_global_get_in_data_offset)
assert_true(forms.numeric_const_in_data_offset)
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(validate_valid_feature_actual_count(stats, ConstExprVariants) > 0)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: ConstExprVariants, min_count: 1, },
]),
[],
)
}
///|
priv struct ValidateTestBasicRefForms {
ref_is_null : Bool
ref_null_none : Bool
ref_null_nofunc : Bool
ref_null_noextern : Bool
ref_test_nullable : Bool
ref_test_nonnullable : Bool
ref_cast_nullable : Bool
ref_cast_nonnullable : Bool
br_on_cast_nullable_target : Bool
br_on_cast_nonnullable_target : Bool
br_on_cast_fail_nullable_target : Bool
br_on_cast_fail_nonnullable_target : Bool
}
///|
fn validate_test_basic_ref_forms_empty() -> ValidateTestBasicRefForms {
{
ref_is_null: false,
ref_null_none: false,
ref_null_nofunc: false,
ref_null_noextern: false,
ref_test_nullable: false,
ref_test_nonnullable: false,
ref_cast_nullable: false,
ref_cast_nonnullable: false,
br_on_cast_nullable_target: false,
br_on_cast_nonnullable_target: false,
br_on_cast_fail_nullable_target: false,
br_on_cast_fail_nonnullable_target: false,
}
}
///|
fn validate_test_basic_ref_forms_merge(
left : ValidateTestBasicRefForms,
right : ValidateTestBasicRefForms,
) -> ValidateTestBasicRefForms {
{
ref_is_null: left.ref_is_null || right.ref_is_null,
ref_null_none: left.ref_null_none || right.ref_null_none,
ref_null_nofunc: left.ref_null_nofunc || right.ref_null_nofunc,
ref_null_noextern: left.ref_null_noextern || right.ref_null_noextern,
ref_test_nullable: left.ref_test_nullable || right.ref_test_nullable,
ref_test_nonnullable: left.ref_test_nonnullable ||
right.ref_test_nonnullable,
ref_cast_nullable: left.ref_cast_nullable || right.ref_cast_nullable,
ref_cast_nonnullable: left.ref_cast_nonnullable ||
right.ref_cast_nonnullable,
br_on_cast_nullable_target: left.br_on_cast_nullable_target ||
right.br_on_cast_nullable_target,
br_on_cast_nonnullable_target: left.br_on_cast_nonnullable_target ||
right.br_on_cast_nonnullable_target,
br_on_cast_fail_nullable_target: left.br_on_cast_fail_nullable_target ||
right.br_on_cast_fail_nullable_target,
br_on_cast_fail_nonnullable_target: left.br_on_cast_fail_nonnullable_target ||
right.br_on_cast_fail_nonnullable_target,
}
}
///|
fn validate_test_basic_ref_forms_ref_null(
rt : RefType,
) -> ValidateTestBasicRefForms {
match rt.get_heap_type() {
AbsHeapTypeHeapType(NoneAbsHeapType) =>
{ ..validate_test_basic_ref_forms_empty(), ref_null_none: true, }
AbsHeapTypeHeapType(NoFuncAbsHeapType) =>
{ ..validate_test_basic_ref_forms_empty(), ref_null_nofunc: true, }
AbsHeapTypeHeapType(NoExternAbsHeapType) =>
{ ..validate_test_basic_ref_forms_empty(), ref_null_noextern: true, }
_ => validate_test_basic_ref_forms_empty()
}
}
///|
fn validate_test_basic_ref_forms_expr(expr : Expr) -> ValidateTestBasicRefForms {
let Expr(instrs) = expr
let mut forms = validate_test_basic_ref_forms_empty()
for instr in instrs {
forms = validate_test_basic_ref_forms_merge(
forms,
validate_test_basic_ref_forms_instr(instr),
)
}
forms
}
///|
fn validate_test_basic_ref_forms_instr(
instr : Instruction,
) -> ValidateTestBasicRefForms {
match instr {
RefIsNull => { ..validate_test_basic_ref_forms_empty(), ref_is_null: true, }
RefNull(rt) => validate_test_basic_ref_forms_ref_null(rt)
RefTest(nullable, _, _) =>
{
..validate_test_basic_ref_forms_empty(),
ref_test_nullable: nullable,
ref_test_nonnullable: !nullable,
}
RefCast(nullable, _, _) =>
{
..validate_test_basic_ref_forms_empty(),
ref_cast_nullable: nullable,
ref_cast_nonnullable: !nullable,
}
BrOnCast(_, CastOp(_, target_nullable, _, _), _, _) =>
{
..validate_test_basic_ref_forms_empty(),
br_on_cast_nullable_target: target_nullable,
br_on_cast_nonnullable_target: !target_nullable,
}
BrOnCastFail(_, CastOp(_, target_nullable, _, _), _, _) =>
{
..validate_test_basic_ref_forms_empty(),
br_on_cast_fail_nullable_target: target_nullable,
br_on_cast_fail_nonnullable_target: !target_nullable,
}
Block(_, body) | Loop(_, body) => validate_test_basic_ref_forms_expr(body)
If(_, then_body, else_body) => {
let mut forms = validate_test_basic_ref_forms_expr(Expr::new(then_body))
match else_body {
Some(branch) =>
forms = validate_test_basic_ref_forms_merge(
forms,
validate_test_basic_ref_forms_expr(Expr::new(branch)),
)
None => ()
}
forms
}
TryTable(_, _, body) => validate_test_basic_ref_forms_expr(body)
_ => validate_test_basic_ref_forms_empty()
}
}
///|
fn validate_test_basic_ref_forms_module(
mod : Module,
) -> ValidateTestBasicRefForms {
let mut forms = validate_test_basic_ref_forms_empty()
match mod.code_sec {
Some(CodeSec(funcs)) =>
for func in funcs {
let Func(_, body) = func
forms = validate_test_basic_ref_forms_merge(
forms,
validate_test_basic_ref_forms_expr(body),
)
}
None => ()
}
forms
}
///|
test "gen_valid coverage-forced emits basic ref instruction surface" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf2600bUL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let forms = validate_test_basic_ref_forms_module(mod)
assert_true(forms.ref_is_null)
assert_true(forms.ref_null_none)
assert_true(forms.ref_null_nofunc)
assert_true(forms.ref_null_noextern)
assert_true(forms.ref_test_nullable)
assert_true(forms.ref_test_nonnullable)
assert_true(forms.ref_cast_nullable)
assert_true(forms.ref_cast_nonnullable)
assert_true(forms.br_on_cast_nullable_target)
assert_true(forms.br_on_cast_nonnullable_target)
assert_true(forms.br_on_cast_fail_nullable_target)
assert_true(forms.br_on_cast_fail_nonnullable_target)
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(
validate_valid_feature_actual_count(stats, BasicRefInstructions) > 0,
)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: BasicRefInstructions, min_count: 1, },
]),
[],
)
}
///|
priv struct ValidateTestI31ExternForms {
ref_i31 : Bool
i31_get_s : Bool
i31_get_u : Bool
any_convert_extern : Bool
extern_convert_any : Bool
}
///|
fn validate_test_i31_extern_forms_empty() -> ValidateTestI31ExternForms {
{
ref_i31: false,
i31_get_s: false,
i31_get_u: false,
any_convert_extern: false,
extern_convert_any: false,
}
}
///|
fn validate_test_i31_extern_forms_merge(
left : ValidateTestI31ExternForms,
right : ValidateTestI31ExternForms,
) -> ValidateTestI31ExternForms {
{
ref_i31: left.ref_i31 || right.ref_i31,
i31_get_s: left.i31_get_s || right.i31_get_s,
i31_get_u: left.i31_get_u || right.i31_get_u,
any_convert_extern: left.any_convert_extern || right.any_convert_extern,
extern_convert_any: left.extern_convert_any || right.extern_convert_any,
}
}
///|
fn validate_test_i31_extern_forms_expr(
expr : Expr,
) -> ValidateTestI31ExternForms {
let Expr(instrs) = expr
let mut forms = validate_test_i31_extern_forms_empty()
for instr in instrs {
forms = validate_test_i31_extern_forms_merge(
forms,
validate_test_i31_extern_forms_instr(instr),
)
}
forms
}
///|
fn validate_test_i31_extern_forms_instr(
instr : Instruction,
) -> ValidateTestI31ExternForms {
match instr {
RefI31 => { ..validate_test_i31_extern_forms_empty(), ref_i31: true, }
I31GetS => { ..validate_test_i31_extern_forms_empty(), i31_get_s: true, }
I31GetU => { ..validate_test_i31_extern_forms_empty(), i31_get_u: true, }
AnyConvertExtern =>
{ ..validate_test_i31_extern_forms_empty(), any_convert_extern: true, }
ExternConvertAny =>
{ ..validate_test_i31_extern_forms_empty(), extern_convert_any: true, }
Block(_, body) | Loop(_, body) => validate_test_i31_extern_forms_expr(body)
If(_, then_body, else_body) => {
let mut forms = validate_test_i31_extern_forms_expr(Expr::new(then_body))
match else_body {
Some(branch) =>
forms = validate_test_i31_extern_forms_merge(
forms,
validate_test_i31_extern_forms_expr(Expr::new(branch)),
)
None => ()
}
forms
}
TryTable(_, _, body) => validate_test_i31_extern_forms_expr(body)
_ => validate_test_i31_extern_forms_empty()
}
}
///|
fn validate_test_i31_extern_forms_module(
mod : Module,
) -> ValidateTestI31ExternForms {
let mut forms = validate_test_i31_extern_forms_empty()
match mod.code_sec {
Some(CodeSec(funcs)) =>
for func in funcs {
let Func(_, body) = func
forms = validate_test_i31_extern_forms_merge(
forms,
validate_test_i31_extern_forms_expr(body),
)
}
None => ()
}
forms
}
///|
test "gen_valid coverage-forced emits i31 and extern conversion surface" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26010UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let forms = validate_test_i31_extern_forms_module(mod)
assert_true(forms.ref_i31)
assert_true(forms.i31_get_s)
assert_true(forms.i31_get_u)
assert_true(forms.any_convert_extern)
assert_true(forms.extern_convert_any)
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(
validate_valid_feature_actual_count(stats, I31ExternConversions) > 0,
)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: I31ExternConversions, min_count: 1, },
]),
[],
)
}
///|
priv struct ValidateTestSimdPhase1Forms {
splat : Bool
extract_lane : Bool
replace_lane : Bool
bitwise : Bool
any_true : Bool
}
///|
fn validate_test_simd_phase1_forms_empty() -> ValidateTestSimdPhase1Forms {
{
splat: false,
extract_lane: false,
replace_lane: false,
bitwise: false,
any_true: false,
}
}
///|
fn validate_test_simd_phase1_forms_merge(
left : ValidateTestSimdPhase1Forms,
right : ValidateTestSimdPhase1Forms,
) -> ValidateTestSimdPhase1Forms {
{
splat: left.splat || right.splat,
extract_lane: left.extract_lane || right.extract_lane,
replace_lane: left.replace_lane || right.replace_lane,
bitwise: left.bitwise || right.bitwise,
any_true: left.any_true || right.any_true,
}
}
///|
fn validate_test_simd_phase1_forms_expr(
expr : Expr,
) -> ValidateTestSimdPhase1Forms {
let Expr(instrs) = expr
let mut forms = validate_test_simd_phase1_forms_empty()
for instr in instrs {
forms = validate_test_simd_phase1_forms_merge(
forms,
validate_test_simd_phase1_forms_instr(instr),
)
}
forms
}
///|
fn validate_test_simd_phase1_forms_instr(
instr : Instruction,
) -> ValidateTestSimdPhase1Forms {
match instr {
I8x16Splat
| I16x8Splat
| I32x4Splat
| I64x2Splat
| F32x4Splat
| F64x2Splat => { ..validate_test_simd_phase1_forms_empty(), splat: true, }
I8x16ExtractLaneS(_)
| I8x16ExtractLaneU(_)
| I16x8ExtractLaneS(_)
| I16x8ExtractLaneU(_)
| I32x4ExtractLane(_)
| I64x2ExtractLane(_)
| F32x4ExtractLane(_)
| F64x2ExtractLane(_) =>
{ ..validate_test_simd_phase1_forms_empty(), extract_lane: true, }
I8x16ReplaceLane(_)
| I16x8ReplaceLane(_)
| I32x4ReplaceLane(_)
| I64x2ReplaceLane(_)
| F32x4ReplaceLane(_)
| F64x2ReplaceLane(_) =>
{ ..validate_test_simd_phase1_forms_empty(), replace_lane: true, }
V128Not | V128And | V128Andnot | V128Or | V128Xor | V128Bitselect =>
{ ..validate_test_simd_phase1_forms_empty(), bitwise: true, }
V128AnyTrue =>
{ ..validate_test_simd_phase1_forms_empty(), any_true: true, }
Block(_, body) | Loop(_, body) => validate_test_simd_phase1_forms_expr(body)
If(_, then_body, else_body) => {
let mut forms = validate_test_simd_phase1_forms_expr(Expr::new(then_body))
match else_body {
Some(branch) =>
forms = validate_test_simd_phase1_forms_merge(
forms,
validate_test_simd_phase1_forms_expr(Expr::new(branch)),
)
None => ()
}
forms
}
TryTable(_, _, body) => validate_test_simd_phase1_forms_expr(body)
_ => validate_test_simd_phase1_forms_empty()
}
}
///|
fn validate_test_simd_phase1_forms_module(
mod : Module,
) -> ValidateTestSimdPhase1Forms {
let mut forms = validate_test_simd_phase1_forms_empty()
match mod.code_sec {
Some(CodeSec(funcs)) =>
for func in funcs {
let Func(_, body) = func
forms = validate_test_simd_phase1_forms_merge(
forms,
validate_test_simd_phase1_forms_expr(body),
)
}
None => ()
}
forms
}
///|
test "gen_valid coverage-forced emits simd phase 1 surface" {
let base = GenValidConfig::coverage_forced_default()
let config = GenValidConfig::{
..base,
max_imports: 0,
section_bias: GenValidSectionBias::{
..base.section_bias,
imports_percent: 0,
},
features: GenValidFeatureToggles::{
..base.features,
allow_typed_body_generator: true,
},
}
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26014UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let forms = validate_test_simd_phase1_forms_module(mod)
assert_true(forms.splat)
assert_true(forms.extract_lane)
assert_true(forms.replace_lane)
assert_true(forms.bitwise)
assert_true(forms.any_true)
let facts = gen_valid_feature_facts(mod, config.mode)
assert_true(facts.has_simd_phase1)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(validate_valid_feature_actual_count(stats, SimdPhase1) > 0)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: SimdPhase1, min_count: 1, },
]),
[],
)
}
///|
priv struct ValidateTestSimdPhase2Forms {
comparison : Bool
all_true_or_bitmask : Bool
arithmetic : Bool
saturating : Bool
min_max_avgr : Bool
rounding : Bool
}
///|
fn validate_test_simd_phase2_forms_empty() -> ValidateTestSimdPhase2Forms {
{
comparison: false,
all_true_or_bitmask: false,
arithmetic: false,
saturating: false,
min_max_avgr: false,
rounding: false,
}
}
///|
fn validate_test_simd_phase2_forms_merge(
left : ValidateTestSimdPhase2Forms,
right : ValidateTestSimdPhase2Forms,
) -> ValidateTestSimdPhase2Forms {
{
comparison: left.comparison || right.comparison,
all_true_or_bitmask: left.all_true_or_bitmask || right.all_true_or_bitmask,
arithmetic: left.arithmetic || right.arithmetic,
saturating: left.saturating || right.saturating,
min_max_avgr: left.min_max_avgr || right.min_max_avgr,
rounding: left.rounding || right.rounding,
}
}
///|
fn validate_test_simd_phase2_forms_instr(
instr : Instruction,
) -> ValidateTestSimdPhase2Forms {
match instr {
I8x16Eq | I16x8LtS | I32x4GtU | I64x2GeS | F32x4Lt | F64x2Ge =>
{ ..validate_test_simd_phase2_forms_empty(), comparison: true, }
I8x16AllTrue | I16x8Bitmask | I32x4AllTrue | I64x2Bitmask =>
{ ..validate_test_simd_phase2_forms_empty(), all_true_or_bitmask: true, }
I8x16Add
| I16x8Mul
| I32x4Add
| I32x4Mul
| I64x2Add
| I64x2Mul
| F32x4Add
| F32x4Div
| F64x2Add
| F64x2Div =>
{ ..validate_test_simd_phase2_forms_empty(), arithmetic: true, }
I8x16AddSatS | I8x16SubSatU | I16x8AddSatU | I16x8SubSatS =>
{ ..validate_test_simd_phase2_forms_empty(), saturating: true, }
I8x16MinS
| I8x16MaxU
| I8x16AvgrU
| I16x8MinU
| I16x8MaxS
| I16x8AvgrU
| I32x4MinS
| I32x4MaxU
| F32x4Min
| F32x4Max
| F64x2Min
| F64x2Max =>
{ ..validate_test_simd_phase2_forms_empty(), min_max_avgr: true, }
F32x4Ceil
| F32x4Floor
| F32x4Trunc
| F32x4Nearest
| F64x2Ceil
| F64x2Floor
| F64x2Trunc
| F64x2Nearest =>
{ ..validate_test_simd_phase2_forms_empty(), rounding: true, }
Block(_, body) | Loop(_, body) => validate_test_simd_phase2_forms_expr(body)
If(_, then_body, else_body) => {
let mut forms = validate_test_simd_phase2_forms_expr(Expr::new(then_body))
match else_body {
Some(branch) =>
forms = validate_test_simd_phase2_forms_merge(
forms,
validate_test_simd_phase2_forms_expr(Expr::new(branch)),
)
None => ()
}
forms
}
TryTable(_, _, body) => validate_test_simd_phase2_forms_expr(body)
_ => validate_test_simd_phase2_forms_empty()
}
}
///|
fn validate_test_simd_phase2_forms_expr(
expr : Expr,
) -> ValidateTestSimdPhase2Forms {
let Expr(instrs) = expr
let mut forms = validate_test_simd_phase2_forms_empty()
for instr in instrs {
forms = validate_test_simd_phase2_forms_merge(
forms,
validate_test_simd_phase2_forms_instr(instr),
)
}
forms
}
///|
fn validate_test_simd_phase2_forms_module(
mod : Module,
) -> ValidateTestSimdPhase2Forms {
let mut forms = validate_test_simd_phase2_forms_empty()
match mod.code_sec {
Some(CodeSec(funcs)) =>
for func in funcs {
let Func(_, body) = func
forms = validate_test_simd_phase2_forms_merge(
forms,
validate_test_simd_phase2_forms_expr(body),
)
}
None => ()
}
forms
}
///|
test "gen_valid coverage-forced emits simd phase 2 surface" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26015UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let forms = validate_test_simd_phase2_forms_module(mod)
assert_true(forms.comparison)
assert_true(forms.all_true_or_bitmask)
assert_true(forms.arithmetic)
assert_true(forms.saturating)
assert_true(forms.min_max_avgr)
assert_true(forms.rounding)
let facts = gen_valid_feature_facts(mod, config.mode)
assert_true(facts.has_simd_phase2)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(validate_valid_feature_actual_count(stats, SimdPhase2) > 0)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: SimdPhase2, min_count: 1, },
]),
[],
)
}
///|
priv struct ValidateTestSimdPhase3Forms {
memory : Bool
shuffle_swizzle : Bool
narrow_widen_dot_pairwise : Bool
float_demote_promote : Bool
}
///|
fn validate_test_simd_phase3_forms_empty() -> ValidateTestSimdPhase3Forms {
{
memory: false,
shuffle_swizzle: false,
narrow_widen_dot_pairwise: false,
float_demote_promote: false,
}
}
///|
fn validate_test_simd_phase3_forms_merge(
left : ValidateTestSimdPhase3Forms,
right : ValidateTestSimdPhase3Forms,
) -> ValidateTestSimdPhase3Forms {
{
memory: left.memory || right.memory,
shuffle_swizzle: left.shuffle_swizzle || right.shuffle_swizzle,
narrow_widen_dot_pairwise: left.narrow_widen_dot_pairwise ||
right.narrow_widen_dot_pairwise,
float_demote_promote: left.float_demote_promote ||
right.float_demote_promote,
}
}
///|
fn validate_test_simd_phase3_forms_instr(
instr : Instruction,
) -> ValidateTestSimdPhase3Forms {
match instr {
V128Load(_)
| V128Load8x8S(_)
| V128Load8x8U(_)
| V128Load16x4S(_)
| V128Load16x4U(_)
| V128Load32x2S(_)
| V128Load32x2U(_)
| V128Load8Splat(_)
| V128Load16Splat(_)
| V128Load32Splat(_)
| V128Load64Splat(_)
| V128Store(_)
| V128Load8Lane(_, _)
| V128Load16Lane(_, _)
| V128Load32Lane(_, _)
| V128Load64Lane(_, _)
| V128Store8Lane(_, _)
| V128Store16Lane(_, _)
| V128Store32Lane(_, _)
| V128Store64Lane(_, _)
| V128Load32Zero(_)
| V128Load64Zero(_) =>
{ ..validate_test_simd_phase3_forms_empty(), memory: true, }
I8x16Shuffle(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)
| I8x16Swizzle =>
{ ..validate_test_simd_phase3_forms_empty(), shuffle_swizzle: true, }
I8x16NarrowI16x8S
| I8x16NarrowI16x8U
| I16x8NarrowI32x4S
| I16x8NarrowI32x4U
| I16x8ExtaddPairwiseI8x16S
| I16x8ExtaddPairwiseI8x16U
| I32x4ExtaddPairwiseI16x8S
| I32x4ExtaddPairwiseI16x8U
| I32x4DotI16x8S =>
{
..validate_test_simd_phase3_forms_empty(),
narrow_widen_dot_pairwise: true,
}
F32x4DemoteF64x2Zero | F64x2PromoteLowF32x4 =>
{ ..validate_test_simd_phase3_forms_empty(), float_demote_promote: true, }
Block(_, body) | Loop(_, body) => validate_test_simd_phase3_forms_expr(body)
If(_, then_body, else_body) => {
let mut forms = validate_test_simd_phase3_forms_expr(Expr::new(then_body))
match else_body {
Some(branch) =>
forms = validate_test_simd_phase3_forms_merge(
forms,
validate_test_simd_phase3_forms_expr(Expr::new(branch)),
)
None => ()
}
forms
}
TryTable(_, _, body) => validate_test_simd_phase3_forms_expr(body)
_ => validate_test_simd_phase3_forms_empty()
}
}
///|
fn validate_test_simd_phase3_forms_expr(
expr : Expr,
) -> ValidateTestSimdPhase3Forms {
let Expr(instrs) = expr
let mut forms = validate_test_simd_phase3_forms_empty()
for instr in instrs {
forms = validate_test_simd_phase3_forms_merge(
forms,
validate_test_simd_phase3_forms_instr(instr),
)
}
forms
}
///|
fn validate_test_simd_phase3_forms_module(
mod : Module,
) -> ValidateTestSimdPhase3Forms {
let mut forms = validate_test_simd_phase3_forms_empty()
match mod.code_sec {
Some(CodeSec(funcs)) =>
for func in funcs {
let Func(_, body) = func
forms = validate_test_simd_phase3_forms_merge(
forms,
validate_test_simd_phase3_forms_expr(body),
)
}
None => ()
}
forms
}
///|
fn validate_test_atomic_load_form(instr : Instruction) -> Int32? {
match instr {
I32AtomicLoad(_, _) => Some(0)
I64AtomicLoad(_, _) => Some(1)
I32AtomicLoad8U(_, _) => Some(2)
I32AtomicLoad16U(_, _) => Some(3)
I64AtomicLoad8U(_, _) => Some(4)
I64AtomicLoad16U(_, _) => Some(5)
I64AtomicLoad32U(_, _) => Some(6)
_ => None
}
}
///|
fn validate_test_atomic_store_form(instr : Instruction) -> Int32? {
match instr {
I32AtomicStore(_, _) => Some(0)
I64AtomicStore(_, _) => Some(1)
I32AtomicStore8(_, _) => Some(2)
I32AtomicStore16(_, _) => Some(3)
I64AtomicStore8(_, _) => Some(4)
I64AtomicStore16(_, _) => Some(5)
I64AtomicStore32(_, _) => Some(6)
_ => None
}
}
///|
fn validate_test_atomic_rmw_form(op : AtomicRmwOp) -> Int32 {
match op {
I32AtomicRmwAddOp => 0
I64AtomicRmwAddOp => 1
I32AtomicRmw8AddUOp => 2
I32AtomicRmw16AddUOp => 3
I64AtomicRmw8AddUOp => 4
I64AtomicRmw16AddUOp => 5
I64AtomicRmw32AddUOp => 6
I32AtomicRmwSubOp => 7
I64AtomicRmwSubOp => 8
I32AtomicRmw8SubUOp => 9
I32AtomicRmw16SubUOp => 10
I64AtomicRmw8SubUOp => 11
I64AtomicRmw16SubUOp => 12
I64AtomicRmw32SubUOp => 13
I32AtomicRmwAndOp => 14
I64AtomicRmwAndOp => 15
I32AtomicRmw8AndUOp => 16
I32AtomicRmw16AndUOp => 17
I64AtomicRmw8AndUOp => 18
I64AtomicRmw16AndUOp => 19
I64AtomicRmw32AndUOp => 20
I32AtomicRmwOrOp => 21
I64AtomicRmwOrOp => 22
I32AtomicRmw8OrUOp => 23
I32AtomicRmw16OrUOp => 24
I64AtomicRmw8OrUOp => 25
I64AtomicRmw16OrUOp => 26
I64AtomicRmw32OrUOp => 27
I32AtomicRmwXorOp => 28
I64AtomicRmwXorOp => 29
I32AtomicRmw8XorUOp => 30
I32AtomicRmw16XorUOp => 31
I64AtomicRmw8XorUOp => 32
I64AtomicRmw16XorUOp => 33
I64AtomicRmw32XorUOp => 34
I32AtomicRmwXchgOp => 35
I64AtomicRmwXchgOp => 36
I32AtomicRmw8XchgUOp => 37
I32AtomicRmw16XchgUOp => 38
I64AtomicRmw8XchgUOp => 39
I64AtomicRmw16XchgUOp => 40
I64AtomicRmw32XchgUOp => 41
}
}
///|
fn validate_test_atomic_cmpxchg_form(op : AtomicCmpxchgOp) -> Int32 {
match op {
I32AtomicRmwCmpxchgOp => 0
I64AtomicRmwCmpxchgOp => 1
I32AtomicRmw8CmpxchgUOp => 2
I32AtomicRmw16CmpxchgUOp => 3
I64AtomicRmw8CmpxchgUOp => 4
I64AtomicRmw16CmpxchgUOp => 5
I64AtomicRmw32CmpxchgUOp => 6
}
}
///|
fn validate_test_bitset_all_seen(bits : @bitset.BitSet) -> Bool {
for i = 0; i < bits.length(); i = i + 1 {
if !bits.at(i) {
return false
}
}
true
}
///|
test "gen_valid coverage-forced emits full atomic instruction matrix" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26017UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let loads = @bitset.BitSet::new(7)
let stores = @bitset.BitSet::new(7)
let rmws = @bitset.BitSet::new(42)
let cmpxchgs = @bitset.BitSet::new(7)
let mut saw_notify = false
let mut saw_wait32 = false
let mut saw_wait64 = false
let mut saw_fence = false
let mut saw_nonzero_shared_memidx = false
match mod.code_sec {
Some(CodeSec(funcs)) =>
for func in funcs {
let Func(_, Expr(instrs)) = func
for instr in instrs {
match validate_test_atomic_load_form(instr) {
Some(idx) => loads.set(idx, true)
None => ()
}
match validate_test_atomic_store_form(instr) {
Some(idx) => stores.set(idx, true)
None => ()
}
match instr {
AtomicRmw(_, op, ma) => {
rmws.set(validate_test_atomic_rmw_form(op), true)
let MemArg(_, mem, _) = ma
match mem {
Some(MemIdx(idx)) if idx > 0U =>
saw_nonzero_shared_memidx = true
_ => ()
}
}
AtomicCmpxchg(_, op, ma) => {
cmpxchgs.set(validate_test_atomic_cmpxchg_form(op), true)
let MemArg(_, mem, _) = ma
match mem {
Some(MemIdx(idx)) if idx > 0U =>
saw_nonzero_shared_memidx = true
_ => ()
}
}
MemoryAtomicNotify(_) => saw_notify = true
MemoryAtomicWait32(_) => saw_wait32 = true
MemoryAtomicWait64(_) => saw_wait64 = true
AtomicFence(_) => saw_fence = true
_ => ()
}
}
}
None => ()
}
assert_true(validate_test_bitset_all_seen(loads))
assert_true(validate_test_bitset_all_seen(stores))
assert_true(validate_test_bitset_all_seen(rmws))
assert_true(validate_test_bitset_all_seen(cmpxchgs))
assert_true(saw_notify)
assert_true(saw_wait32)
assert_true(saw_wait64)
assert_true(saw_fence)
assert_true(saw_nonzero_shared_memidx)
}
///|
test "gen_valid coverage-forced emits atomic instruction surface" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26017UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let facts = gen_valid_feature_facts(mod, config.mode)
assert_true(facts.has_atomics)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(validate_valid_feature_actual_count(stats, Atomics) > 0)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [{ key: Atomics, min_count: 1, }]),
[],
)
}
///|
test "gen_valid coverage-forced emits simd phase 3 surface" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26016UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let forms = validate_test_simd_phase3_forms_module(mod)
assert_true(forms.memory)
assert_true(forms.shuffle_swizzle)
assert_true(forms.narrow_widen_dot_pairwise)
assert_true(forms.float_demote_promote)
let facts = gen_valid_feature_facts(mod, config.mode)
assert_true(facts.has_simd_phase3)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(validate_valid_feature_actual_count(stats, SimdPhase3) > 0)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: SimdPhase3, min_count: 1, },
]),
[],
)
}
///|
priv struct ValidateTestGcConstructorAccessorForms {
struct_new : Bool
array_new : Bool
array_new_fixed : Bool
struct_get_s : Bool
struct_get_u : Bool
array_get_s : Bool
array_get_u : Bool
array_fill : Bool
array_copy : Bool
}
///|
fn validate_test_gc_constructor_accessor_forms_empty() -> ValidateTestGcConstructorAccessorForms {
{
struct_new: false,
array_new: false,
array_new_fixed: false,
struct_get_s: false,
struct_get_u: false,
array_get_s: false,
array_get_u: false,
array_fill: false,
array_copy: false,
}
}
///|
fn validate_test_gc_constructor_accessor_forms_merge(
left : ValidateTestGcConstructorAccessorForms,
right : ValidateTestGcConstructorAccessorForms,
) -> ValidateTestGcConstructorAccessorForms {
{
struct_new: left.struct_new || right.struct_new,
array_new: left.array_new || right.array_new,
array_new_fixed: left.array_new_fixed || right.array_new_fixed,
struct_get_s: left.struct_get_s || right.struct_get_s,
struct_get_u: left.struct_get_u || right.struct_get_u,
array_get_s: left.array_get_s || right.array_get_s,
array_get_u: left.array_get_u || right.array_get_u,
array_fill: left.array_fill || right.array_fill,
array_copy: left.array_copy || right.array_copy,
}
}
///|
fn validate_test_gc_constructor_accessor_forms_expr(
expr : Expr,
) -> ValidateTestGcConstructorAccessorForms {
let Expr(instrs) = expr
let mut forms = validate_test_gc_constructor_accessor_forms_empty()
for instr in instrs {
forms = validate_test_gc_constructor_accessor_forms_merge(
forms,
validate_test_gc_constructor_accessor_forms_instr(instr),
)
}
forms
}
///|
fn validate_test_gc_constructor_accessor_forms_instr(
instr : Instruction,
) -> ValidateTestGcConstructorAccessorForms {
match instr {
StructNew(_) =>
{
..validate_test_gc_constructor_accessor_forms_empty(),
struct_new: true,
}
ArrayNew(_) =>
{
..validate_test_gc_constructor_accessor_forms_empty(),
array_new: true,
}
ArrayNewFixed(_, _) =>
{
..validate_test_gc_constructor_accessor_forms_empty(),
array_new_fixed: true,
}
StructGetS(_, _) =>
{
..validate_test_gc_constructor_accessor_forms_empty(),
struct_get_s: true,
}
StructGetU(_, _) =>
{
..validate_test_gc_constructor_accessor_forms_empty(),
struct_get_u: true,
}
ArrayGetS(_) =>
{
..validate_test_gc_constructor_accessor_forms_empty(),
array_get_s: true,
}
ArrayGetU(_) =>
{
..validate_test_gc_constructor_accessor_forms_empty(),
array_get_u: true,
}
ArrayFill(_) =>
{
..validate_test_gc_constructor_accessor_forms_empty(),
array_fill: true,
}
ArrayCopy(_, _) =>
{
..validate_test_gc_constructor_accessor_forms_empty(),
array_copy: true,
}
Block(_, body) | Loop(_, body) =>
validate_test_gc_constructor_accessor_forms_expr(body)
If(_, then_body, else_body) => {
let mut forms = validate_test_gc_constructor_accessor_forms_expr(
Expr::new(then_body),
)
match else_body {
Some(branch) =>
forms = validate_test_gc_constructor_accessor_forms_merge(
forms,
validate_test_gc_constructor_accessor_forms_expr(Expr::new(branch)),
)
None => ()
}
forms
}
TryTable(_, _, body) =>
validate_test_gc_constructor_accessor_forms_expr(body)
_ => validate_test_gc_constructor_accessor_forms_empty()
}
}
///|
fn validate_test_gc_constructor_accessor_forms_module(
mod : Module,
) -> ValidateTestGcConstructorAccessorForms {
let mut forms = validate_test_gc_constructor_accessor_forms_empty()
match mod.code_sec {
Some(CodeSec(funcs)) =>
for func in funcs {
let Func(_, body) = func
forms = validate_test_gc_constructor_accessor_forms_merge(
forms,
validate_test_gc_constructor_accessor_forms_expr(body),
)
}
None => ()
}
forms
}
///|
test "gen_valid coverage-forced emits gc constructor and accessor surface" {
let config = GenValidConfig::{
..GenValidConfig::coverage_forced_default(),
max_type_defs: 24,
}
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26011UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let forms = validate_test_gc_constructor_accessor_forms_module(mod)
assert_true(forms.struct_new)
assert_true(forms.array_new)
assert_true(forms.array_new_fixed)
assert_true(forms.struct_get_s)
assert_true(forms.struct_get_u)
assert_true(forms.array_get_s)
assert_true(forms.array_get_u)
assert_true(forms.array_fill)
assert_true(forms.array_copy)
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(validate_valid_feature_actual_count(stats, GcConstructors) > 0)
assert_true(validate_valid_feature_actual_count(stats, GcAccessors) > 0)
assert_true(validate_valid_feature_actual_count(stats, StringOps) > 0)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: GcConstructors, min_count: 1, },
{ key: GcAccessors, min_count: 1, },
{ key: StringOps, min_count: 1, },
]),
[],
)
}
///|
test "gen_valid coverage-forced emits subtyping topology surface" {
let config = GenValidConfig::{
..GenValidConfig::coverage_forced_default(),
max_type_defs: 24,
}
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26018UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(validate_valid_feature_actual_count(stats, SubtypingTopology) > 0)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: SubtypingTopology, min_count: 1, },
]),
[],
)
}
///|
test "gen_valid coverage-forced emits rich GC field plans" {
let config = GenValidConfig::{
..GenValidConfig::coverage_forced_default(),
max_type_defs: 24,
}
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26019UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let facts = gen_valid_feature_facts(mod, config.mode)
assert_true(facts.has_rich_gc_field_plans)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(validate_valid_feature_actual_count(stats, RichGcFieldPlans) > 0)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: RichGcFieldPlans, min_count: 1, },
]),
[],
)
}
///|
priv struct ValidateTestExceptionMatrixFacts {
imported_tags : Int32
defined_tags : Int32
param_tag_types : Int32
catch_tags : Int32
catch_ref_tags : Int32
catch_all : Int32
catch_all_ref : Int32
catch_label_depth_gt0 : Int32
max_catches : Int32
try_tables : Int32
nested_try_tables : Int32
numeric_result_try_tables : Int32
ref_result_try_tables : Int32
throws : Int32
throw_refs : Int32
block_sourced_throw_refs : Int32
local_sourced_throw_refs : Int32
}
///|
fn validate_test_exception_matrix_empty() -> ValidateTestExceptionMatrixFacts {
{
imported_tags: 0,
defined_tags: 0,
param_tag_types: 0,
catch_tags: 0,
catch_ref_tags: 0,
catch_all: 0,
catch_all_ref: 0,
catch_label_depth_gt0: 0,
max_catches: 0,
try_tables: 0,
nested_try_tables: 0,
numeric_result_try_tables: 0,
ref_result_try_tables: 0,
throws: 0,
throw_refs: 0,
block_sourced_throw_refs: 0,
local_sourced_throw_refs: 0,
}
}
///|
fn validate_test_exception_matrix_merge(
a : ValidateTestExceptionMatrixFacts,
b : ValidateTestExceptionMatrixFacts,
) -> ValidateTestExceptionMatrixFacts {
{
imported_tags: a.imported_tags + b.imported_tags,
defined_tags: a.defined_tags + b.defined_tags,
param_tag_types: a.param_tag_types + b.param_tag_types,
catch_tags: a.catch_tags + b.catch_tags,
catch_ref_tags: a.catch_ref_tags + b.catch_ref_tags,
catch_all: a.catch_all + b.catch_all,
catch_all_ref: a.catch_all_ref + b.catch_all_ref,
catch_label_depth_gt0: a.catch_label_depth_gt0 + b.catch_label_depth_gt0,
max_catches: if a.max_catches > b.max_catches {
a.max_catches
} else {
b.max_catches
},
try_tables: a.try_tables + b.try_tables,
nested_try_tables: a.nested_try_tables + b.nested_try_tables,
numeric_result_try_tables: a.numeric_result_try_tables +
b.numeric_result_try_tables,
ref_result_try_tables: a.ref_result_try_tables + b.ref_result_try_tables,
throws: a.throws + b.throws,
throw_refs: a.throw_refs + b.throw_refs,
block_sourced_throw_refs: a.block_sourced_throw_refs +
b.block_sourced_throw_refs,
local_sourced_throw_refs: a.local_sourced_throw_refs +
b.local_sourced_throw_refs,
}
}
///|
fn validate_test_exception_matrix_facts_expr(
expr : Expr,
inside_try~ : Bool = false,
) -> ValidateTestExceptionMatrixFacts {
let Expr(instrs) = expr
let mut facts = validate_test_exception_matrix_empty()
let mut previous_was_exn_block = false
let mut previous_was_exn_local_get = false
for instr in instrs {
match instr {
TryTable(bt, catches, body) => {
let mut catch_tags = 0
let mut catch_ref_tags = 0
let mut catch_all = 0
let mut catch_all_ref = 0
let mut deep_labels = 0
for catch_ in catches {
match catch_ {
Catch(_, LabelIdx(depth)) => {
catch_tags += 1
if depth > 0U {
deep_labels += 1
}
}
CatchRef(_, LabelIdx(depth)) => {
catch_ref_tags += 1
if depth > 0U {
deep_labels += 1
}
}
CatchAll(LabelIdx(depth)) => {
catch_all += 1
if depth > 0U {
deep_labels += 1
}
}
CatchAllRef(LabelIdx(depth)) => {
catch_all_ref += 1
if depth > 0U {
deep_labels += 1
}
}
}
}
let (numeric_result, ref_result) = match bt {
ValTypeBlockType(vt) =>
(
vt == ValType::i32(),
match vt {
RefTypeValType(_) => true
_ => false
},
)
TypeIdxBlockType(_) => (true, false)
_ => (false, false)
}
facts = validate_test_exception_matrix_merge(facts, {
..validate_test_exception_matrix_empty(),
catch_tags,
catch_ref_tags,
catch_all,
catch_all_ref,
catch_label_depth_gt0: deep_labels,
max_catches: catches.length(),
try_tables: 1,
nested_try_tables: if inside_try {
1
} else {
0
},
numeric_result_try_tables: if numeric_result {
1
} else {
0
},
ref_result_try_tables: if ref_result {
1
} else {
0
},
})
facts = validate_test_exception_matrix_merge(
facts,
validate_test_exception_matrix_facts_expr(body, inside_try=true),
)
previous_was_exn_block = false
previous_was_exn_local_get = false
}
Block(bt, body) | Loop(bt, body) => {
facts = validate_test_exception_matrix_merge(
facts,
validate_test_exception_matrix_facts_expr(body, inside_try~),
)
previous_was_exn_block = bt ==
BlockType::val_type(ValType::ref_null_exn())
previous_was_exn_local_get = false
}
If(_, then_body, else_body) => {
facts = validate_test_exception_matrix_merge(
facts,
validate_test_exception_matrix_facts_expr(
Expr::new(then_body),
inside_try~,
),
)
match else_body {
Some(branch) =>
facts = validate_test_exception_matrix_merge(
facts,
validate_test_exception_matrix_facts_expr(
Expr::new(branch),
inside_try~,
),
)
None => ()
}
previous_was_exn_block = false
previous_was_exn_local_get = false
}
Throw(_) => {
facts = validate_test_exception_matrix_merge(facts, {
..validate_test_exception_matrix_empty(),
throws: 1,
})
previous_was_exn_block = false
previous_was_exn_local_get = false
}
ThrowRef => {
facts = validate_test_exception_matrix_merge(facts, {
..validate_test_exception_matrix_empty(),
throw_refs: 1,
block_sourced_throw_refs: if previous_was_exn_block {
1
} else {
0
},
local_sourced_throw_refs: if previous_was_exn_local_get {
1
} else {
0
},
})
previous_was_exn_block = false
previous_was_exn_local_get = false
}
LocalGet(_) => {
previous_was_exn_block = false
previous_was_exn_local_get = true
}
_ => {
previous_was_exn_block = false
previous_was_exn_local_get = false
}
}
}
facts
}
///|
fn validate_test_exception_matrix_subtype_at(
types : Array[RecType],
idx : Int32,
) -> SubType? {
let mut cursor = 0
for rec_type in types {
match rec_type {
SingleRecType(subtype) => {
if cursor == idx {
return Some(subtype)
}
cursor += 1
}
GroupRecType(group) =>
for subtype in group {
if cursor == idx {
return Some(subtype)
}
cursor += 1
}
}
}
None
}
///|
fn validate_test_exception_matrix_facts_module(
mod : Module,
) -> ValidateTestExceptionMatrixFacts {
let mut facts = validate_test_exception_matrix_empty()
match mod.import_sec {
Some(ImportSec(imports)) =>
for import_ in imports {
let Import(_, _, extern_type) = import_
match extern_type {
TagExternType(_) =>
facts = { ..facts, imported_tags: facts.imported_tags + 1, }
_ => ()
}
}
None => ()
}
let mut tag_types : Array[TagType] = []
match mod.tag_sec {
Some(TagSec(tags)) => {
facts = { ..facts, defined_tags: tags.length(), }
tag_types = tags.copy()
}
None => ()
}
match mod.type_sec {
Some(TypeSec(types)) =>
for tag_type in tag_types {
let TagType(type_idx) = tag_type
let idx = match type_idx {
TypeIdx(u) | RecIdx(u) => u.reinterpret_as_int()
}
match validate_test_exception_matrix_subtype_at(types, idx) {
Some(SubType(_, _, _, FuncCompType(params, _)))
| Some(CompTypeSubType(_, FuncCompType(params, _))) =>
if !params.is_empty() {
facts = { ..facts, param_tag_types: facts.param_tag_types + 1, }
}
_ => ()
}
}
None => ()
}
match mod.code_sec {
Some(CodeSec(funcs)) =>
for func in funcs {
let Func(_, body) = func
facts = validate_test_exception_matrix_merge(
facts,
validate_test_exception_matrix_facts_expr(body),
)
}
None => ()
}
facts
}
///|
test "gen_valid coverage-forced emits exception try_table matrix surface" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26013UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let facts = gen_valid_feature_facts(mod, config.mode)
assert_true(facts.has_exception_try_table_matrix)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(
validate_valid_feature_actual_count(stats, ExceptionTryTableMatrix) > 0,
)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: ExceptionTryTableMatrix, min_count: 1, },
]),
[],
)
let matrix = validate_test_exception_matrix_facts_module(mod)
assert_true(matrix.imported_tags >= 1)
assert_true(matrix.defined_tags >= 2)
assert_true(matrix.param_tag_types >= 1)
assert_true(matrix.catch_tags >= 2)
assert_true(matrix.catch_ref_tags >= 1)
assert_true(matrix.catch_all >= 1)
assert_true(matrix.catch_all_ref >= 1)
assert_true(matrix.catch_label_depth_gt0 >= 1)
assert_true(matrix.max_catches >= 3)
assert_true(matrix.try_tables >= 5)
assert_true(matrix.nested_try_tables >= 1)
assert_true(matrix.numeric_result_try_tables >= 1)
assert_true(matrix.ref_result_try_tables >= 1)
assert_true(matrix.throws >= 3)
assert_true(matrix.throw_refs >= 3)
assert_true(matrix.block_sourced_throw_refs >= 1)
assert_true(matrix.local_sourced_throw_refs >= 1)
}
///|
test "gen_valid coverage-forced emits import/export topology surface" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26020UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let facts = gen_valid_feature_facts(mod, config.mode)
assert_true(facts.has_import_export_topology)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(
validate_valid_feature_actual_count(stats, ImportExportTopology) > 0,
)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: ImportExportTopology, min_count: 1, },
]),
[],
)
}
///|
test "gen_valid coverage-forced emits element segment range surface" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26021UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let facts = gen_valid_feature_facts(mod, config.mode)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(
validate_valid_feature_actual_count(stats, ElementSegmentRange) > 0,
)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: ElementSegmentRange, min_count: 1, },
]),
[],
)
}
///|
test "gen_valid coverage-forced emits data segment range surface" {
let config = GenValidConfig::coverage_forced_default()
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26022UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
let facts = gen_valid_feature_facts(mod, config.mode)
assert_true(facts.has_data_segment_range)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(validate_valid_feature_actual_count(stats, DataSegmentRange) > 0)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: DataSegmentRange, min_count: 1, },
]),
[],
)
}
///|
test "gen_valid coverage-forced emits name and custom section surface" {
let config = GenValidConfig::for_profile(TopologyHeavyProfile)
let mod = gen_valid_module_with_config(@splitmix.new(seed=0xf26023UL), config)
match validate_module(mod) {
Ok(_) => ()
Err(msg) =>
fail("expected coverage-forced gen-valid module to validate, got \{msg}")
}
match mod.name_sec {
Some(name_sec) => {
match name_sec.func_names {
Some(NameMap(entries)) => assert_true(!entries.is_empty())
None =>
fail(
"expected metadata-heavy gen-valid profile to emit function names",
)
}
match name_sec.local_names {
Some(IndirectNameMap(entries)) => assert_true(!entries.is_empty())
None =>
fail("expected metadata-heavy gen-valid profile to emit local names")
}
match name_sec.label_names {
Some(IndirectNameMap(entries)) => assert_true(!entries.is_empty())
None =>
fail("expected metadata-heavy gen-valid profile to emit label names")
}
}
None =>
fail("expected metadata-heavy gen-valid profile to emit a name section")
}
let mut has_non_name_custom = false
for custom in mod.custom_secs {
let @lib.CustomSec(name, _) = custom
if name != Name::new("name") {
has_non_name_custom = true
}
}
assert_true(has_non_name_custom)
let facts = gen_valid_feature_facts(mod, config.mode)
assert_true(facts.has_name_custom_sections)
let stats = GenValidFeatureStats::empty(config.mode).record(facts)
assert_true(
validate_valid_feature_actual_count(stats, NameCustomSections) > 0,
)
@debug.assert_eq(
check_validate_valid_feature_floors(stats, [
{ key: NameCustomSections, min_count: 1, },
]),
[],
)
}
///|
test "run_validate_valid_fuzz rejects unknown profile" {
match run_validate_valid_fuzz("not-a-profile", 0x5eedUL) {
Ok(_) => fail("expected unknown profile error")
Err(msg) => assert_true(msg.contains("unknown validate-valid fuzz profile"))
}
}
///|
#skip("manual validate-valid fuzz smoke; run through the dedicated fuzz lane")
test "run_validate_valid_fuzz returns deterministic stats for a fixed seed" {
let first = match run_validate_valid_fuzz("smoke", 0x5eedUL) {
Ok(v) => v
Err(msg) => fail(msg)
}
let second = match run_validate_valid_fuzz("smoke", 0x5eedUL) {
Ok(v) => v
Err(msg) => fail(msg)
}
@debug.assert_eq(first, second)
@debug.assert_eq(first.attempts, 128)
@debug.assert_eq(first.validated, 128)
@debug.assert_eq(first.generator_config.mode, CoverageForced)
@debug.assert_eq(first.feature_stats.mode, CoverageForced)
@debug.assert_eq(
first.feature_floors,
match validate_valid_run_config("smoke") {
Ok(v) => v.feature_floors
Err(msg) => fail(msg)
},
)
@debug.assert_eq(first.feature_stats.modules, 128)
@debug.assert_eq(first.feature_stats.func_exports, 128)
assert_true(first.feature_stats.imports > 0)
assert_true(first.feature_stats.tables > 0)
assert_true(first.feature_stats.mems > 0)
assert_true(first.feature_stats.globals > 0)
assert_true(first.feature_stats.tags > 0)
assert_true(first.feature_stats.elems > 0)
assert_true(first.feature_stats.datas > 0)
assert_true(first.feature_stats.starts > 0)
@debug.assert_eq(first.feature_stats.lacks_start, 0)
assert_true(first.feature_stats.ref_types > 0)
assert_true(first.feature_stats.v128 > 0)
assert_true(first.feature_stats.calls > 0)
assert_true(first.feature_stats.call_indirect > 0)
assert_true(first.feature_stats.branch_heavy_control > 0)
}
///|
test "gen_valid natural mode organically reaches descriptor/string/exn/cast gc surfaces" {
let rnd = @splitmix.new(seed=0x5eedUL)
let config = default_gen_valid_config()
let mut combined = gen_valid_gc_surface_facts_empty()
for _ in 0..<256 {
let mod = gen_valid_module_with_config(rnd, config)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected natural gen-valid module to validate, got \{validation_error_message(err)} func=\{@debug.to_string(validation_error_func_idx(err))} mod=\{mod}",
)
}
combined = gen_valid_gc_surface_facts_merge(
combined,
gen_valid_gc_surface_facts_module(mod),
)
}
assert_true(combined.struct_types > 0)
assert_true(combined.array_types > 0)
assert_true(combined.descriptor_types > 0)
assert_true(combined.has_string_new)
assert_true(combined.has_string_encode)
assert_true(combined.has_br_on_cast)
assert_true(combined.has_br_on_cast_fail)
assert_true(combined.has_try_table)
assert_true(combined.has_throw)
assert_true(combined.has_throw_ref)
}
///|
test "gen_valid natural mode reaches exact ref globals locals bulk array ops and mixed catch try_tables" {
let rnd = @splitmix.new(seed=0x51eedUL)
let config = GenValidConfig::{
..GenValidConfig::natural_default(),
max_type_defs: 10,
max_tables: 3,
max_mems: 2,
max_tags: 2,
max_elems: 4,
max_datas: 4,
max_body_depth: 3,
section_bias: GenValidSectionBias::{
..GenValidSectionBias::natural_default(),
tables_percent: 100,
mems_percent: 100,
tags_percent: 100,
elems_percent: 100,
datas_percent: 100,
},
}
let mut combined = gen_valid_gc_surface_facts_empty()
for _ in 0..<384 {
let mod = gen_valid_module_with_config(rnd, config)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected widened natural gen-valid module to validate, got \{validation_error_message(err)} func=\{@debug.to_string(validation_error_func_idx(err))} mod=\{mod}",
)
}
combined = gen_valid_gc_surface_facts_merge(
combined,
gen_valid_gc_surface_facts_module(mod),
)
}
assert_true(combined.has_exact_ref_null)
assert_true(combined.exact_typed_ref_globals > 0)
assert_true(combined.exact_typed_ref_locals > 0)
ignore(combined.exact_typed_ref_results)
assert_true(combined.has_array_new_data)
assert_true(combined.has_array_new_elem)
assert_true(combined.has_array_init_data)
assert_true(combined.has_array_init_elem)
assert_true(combined.has_mixed_catch_try_table)
assert_true(combined.has_branchy_mixed_catch_try_table)
}
///|
test "gen_valid_tfunc returns exact refs through a real natural local flow" {
let described_idx = TypeIdx::new(0U)
let exact_vt = ValType::ref_type(
RefType::new(true, HeapType::new(described_idx), exact=true),
)
let descriptor_pair = RecType::group([
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(descriptor=Some(TypeIdx::rec(1))),
),
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::rec(0))),
),
])
let func_type = RecType::new(
SubType::comp_type(CompType::func([], [exact_vt])),
)
let rnd = @splitmix.new(seed=0x5eed5eedUL)
let ctx = GenValidContext::new(rnd, GenValidConfig::natural_default())
let func = gen_valid_tfunc(ctx, [], [exact_vt])
let Func(locals, Expr(instrs)) = func
let mut has_exact_local = false
for run in locals.runs() {
let { count, vt, } = run
ignore(count)
if vt == exact_vt {
has_exact_local = true
}
}
assert_true(has_exact_local)
match instrs.last() {
Some(LocalGet(_)) => ()
_ => fail("expected exact natural return flow to end with local.get")
}
let mod = Module::new()
.with_type_sec(TypeSec::new([descriptor_pair, func_type]))
.with_func_sec(FuncSec::new([TypeIdx::new(2U)]))
.with_code_sec(CodeSec::new([func]))
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected exact-result natural tfunc module to validate, got \{validation_error_message(err)} func=\{@debug.to_string(validation_error_func_idx(err))} mod=\{mod}",
)
}
}
///|
test "gen_valid_build_func reuses an exact param for natural exact-result returns" {
let described_idx = TypeIdx::new(0U)
let exact_rt = RefType::new(true, HeapType::new(described_idx), exact=true)
let exact_vt = ValType::ref_type(exact_rt)
let descriptor_pair = RecType::group([
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(descriptor=Some(TypeIdx::rec(1))),
),
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::rec(0))),
),
])
let func_type = RecType::new(
SubType::comp_type(CompType::func([exact_vt], [exact_vt])),
)
let rnd = @splitmix.new(seed=0x5eedca5aUL)
let ctx = GenValidContext::new(rnd, GenValidConfig::natural_default())
let func = gen_valid_build_func(
ctx,
[exact_vt],
[exact_vt],
[],
[],
[],
[],
[],
[],
[],
[],
[],
None,
gen_valid_gc_type_plan_empty(),
None,
)
let Func(locals, Expr(instrs)) = func
for run in locals.runs() {
let { count, vt, } = run
ignore(count)
assert_false(vt == exact_vt)
}
match instrs.last() {
Some(LocalGet(idx)) => @debug.assert_eq(idx, LocalIdx::new(0U))
_ =>
fail("expected exact natural param return flow to end with local.get 0")
}
let mod = Module::new()
.with_type_sec(TypeSec::new([descriptor_pair, func_type]))
.with_func_sec(FuncSec::new([TypeIdx::new(2U)]))
.with_code_sec(CodeSec::new([func]))
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected exact-result natural param module to validate, got \{validation_error_message(err)} func=\{@debug.to_string(validation_error_func_idx(err))} mod=\{mod}",
)
}
}
///|
test "gen_valid_build_func can return exact refs through a natural direct call path" {
let described_idx = TypeIdx::new(0U)
let exact_rt = RefType::new(true, HeapType::new(described_idx), exact=true)
let exact_vt = ValType::ref_type(exact_rt)
let descriptor_pair = RecType::group([
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(descriptor=Some(TypeIdx::rec(1))),
),
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::rec(0))),
),
])
let func_type = RecType::new(
SubType::comp_type(CompType::func([], [exact_vt])),
)
let helper_sig = {
idx: FuncIdx::new(0U),
type_idx: TypeIdx::new(2U),
params: [],
results: [exact_vt],
}
let rnd = @splitmix.new(seed=0x5eedc411UL)
let ctx = GenValidContext::new(rnd, GenValidConfig::natural_default())
let func = gen_valid_build_func(
ctx,
[],
[exact_vt],
[],
[],
[],
[],
[helper_sig],
[],
[],
[],
[],
None,
gen_valid_gc_type_plan_empty(),
None,
)
let Func(locals, Expr(instrs)) = func
for run in locals.runs() {
let { count, vt, } = run
ignore(count)
assert_false(vt == exact_vt)
}
match instrs.last() {
Some(Call(idx)) => @debug.assert_eq(idx, FuncIdx::new(0U))
_ => fail("expected exact natural return flow to end with call")
}
let mod = Module::new()
.with_type_sec(TypeSec::new([descriptor_pair, func_type]))
.with_import_sec(
ImportSec::new([
Import::new(
Name::new("env"),
Name::new("helper"),
ExternType::func(TypeIdx::new(2U)),
),
]),
)
.with_func_sec(FuncSec::new([TypeIdx::new(2U)]))
.with_code_sec(CodeSec::new([func]))
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected exact-result natural direct-call module to validate, got \{validation_error_message(err)} func=\{@debug.to_string(validation_error_func_idx(err))} mod=\{mod}",
)
}
}
///|
test "gen_valid_build_func can return exact refs through a natural call_ref path" {
let described_idx = TypeIdx::new(0U)
let exact_rt = RefType::new(true, HeapType::new(described_idx), exact=true)
let exact_vt = ValType::ref_type(exact_rt)
let descriptor_pair = RecType::group([
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(descriptor=Some(TypeIdx::rec(1))),
),
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::rec(0))),
),
])
let func_type = RecType::new(
SubType::comp_type(CompType::func([], [exact_vt])),
)
let helper_sig = {
idx: FuncIdx::new(0U),
type_idx: TypeIdx::new(2U),
params: [],
results: [exact_vt],
}
let config = GenValidConfig::{
..GenValidConfig::natural_default(),
features: GenValidFeatureToggles::{
..GenValidFeatureToggles::natural_default(),
allow_calls: false,
},
}
let rnd = @splitmix.new(seed=0x5eedca11UL)
let ctx = GenValidContext::new(rnd, config)
let caller = gen_valid_build_func(
ctx,
[],
[exact_vt],
[],
[],
[],
[],
[helper_sig],
[],
[],
[],
[],
None,
gen_valid_gc_type_plan_empty(),
Some(FuncIdx::new(0U)),
)
let Func(locals, Expr(instrs)) = caller
for run in locals.runs() {
let { count, vt, } = run
ignore(count)
assert_false(vt == exact_vt)
}
match instrs.length() {
n if n >= 2 => {
@debug.assert_eq(instrs[n - 2], Instruction::ref_func(FuncIdx::new(0U)))
@debug.assert_eq(instrs[n - 1], Instruction::call_ref(TypeIdx::new(2U)))
}
_ =>
fail(
"expected exact natural call_ref return flow to emit ref.func + call_ref",
)
}
let helper = Func::new(
Locals::empty(),
Expr::new([Instruction::ref_null_type(exact_rt)]),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([descriptor_pair, func_type]))
.with_func_sec(FuncSec::new([TypeIdx::new(2U), TypeIdx::new(2U)]))
.with_code_sec(CodeSec::new([helper, caller]))
.with_export_sec(
ExportSec::new([
Export::new(Name::new("helper"), ExternIdx::func(FuncIdx::new(0U))),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected exact-result natural call_ref module to validate, got \{validation_error_message(err)} func=\{@debug.to_string(validation_error_func_idx(err))} mod=\{mod}",
)
}
}
///|
#skip("manual aggregate GenValid coverage smoke; run through the dedicated fuzz lane")
test "gen_valid coverage-forced mode emits widened topology and shared type pool" {
let rnd = @splitmix.new(seed=0x5eedUL)
let config = GenValidConfig::coverage_forced_default()
let mut feature_stats = GenValidFeatureStats::empty(CoverageForced)
let mut body_surface = gen_valid_body_surface_facts_empty()
let mut coverage_surface = gen_valid_coverage_surface_facts_empty()
let mut gc_surface = gen_valid_gc_surface_facts_empty()
let mut has_gc_table = false
let mut has_shared_type_pool = false
for _ in 0..<64 {
let mod = gen_valid_module_with_config(rnd, config)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected coverage-forced module to validate, got \{validation_error_message(err)} func=\{@debug.to_string(validation_error_func_idx(err))} mod=\{mod}",
)
}
feature_stats = feature_stats.record(
gen_valid_feature_facts(mod, CoverageForced),
)
body_surface = gen_valid_body_surface_facts_merge(
body_surface,
gen_valid_body_surface_facts_module(mod),
)
coverage_surface = gen_valid_coverage_surface_facts_merge(
coverage_surface,
gen_valid_coverage_surface_facts_module(mod),
)
gc_surface = gen_valid_gc_surface_facts_merge(
gc_surface,
gen_valid_gc_surface_facts_module(mod),
)
match mod.table_sec {
Some(TableSec(tables)) =>
for table in tables {
let Table(TableType(rt, _), _) = table
if gen_valid_gc_surface_ref_score(rt) > 0 &&
rt != RefType::abs(AbsHeapType::func()) {
has_gc_table = true
}
}
None => ()
}
let import_func_count = match mod.import_sec {
Some(ImportSec(imports)) => {
let mut count = 0
for imp in imports {
let Import(_, _, extern_type) = imp
match extern_type {
FuncExternType(_) => count += 1
_ => ()
}
}
count
}
None => 0
}
let defined_func_count = match mod.func_sec {
Some(FuncSec(type_idxs)) => type_idxs.length()
None => 0
}
let tag_count = match mod.tag_sec {
Some(TagSec(tags)) => tags.length()
None => 0
}
let func_type_def_count = match mod.type_sec {
Some(TypeSec(types)) => {
let mut count = 0
for rec_type in types {
match rec_type {
SingleRecType(subtype) =>
if subtype.get_comptype() is FuncCompType(_, _) {
count += 1
}
GroupRecType(group) =>
for subtype in group {
if subtype.get_comptype() is FuncCompType(_, _) {
count += 1
}
}
}
}
count
}
None => 0
}
if import_func_count > 0 &&
defined_func_count > 0 &&
tag_count > 0 &&
func_type_def_count < import_func_count + defined_func_count + tag_count {
has_shared_type_pool = true
}
}
assert_true(feature_stats.imports > 0)
assert_true(feature_stats.func_exports > 0)
assert_true(feature_stats.table_exports > 0)
assert_true(feature_stats.mem_exports > 0)
assert_true(feature_stats.global_exports > 0)
assert_true(feature_stats.tag_exports > 0)
assert_true(feature_stats.starts > 0)
assert_true(feature_stats.tables > 0)
assert_true(feature_stats.mems > 0)
assert_true(feature_stats.globals > 0)
assert_true(feature_stats.tags > 0)
assert_true(feature_stats.elems > 0)
assert_true(feature_stats.datas > 0)
assert_true(feature_stats.data_count > 0)
assert_true(feature_stats.ref_types > 0)
assert_true(feature_stats.v128 > 0)
assert_true(feature_stats.calls > 0)
assert_true(feature_stats.call_indirect > 0)
assert_true(feature_stats.branch_heavy_control > 0)
assert_true(body_surface.has_block)
assert_true(body_surface.has_loop)
assert_true(body_surface.has_if)
assert_true(body_surface.has_br)
assert_true(body_surface.has_br_if)
assert_true(body_surface.has_memory_op)
assert_true(body_surface.has_table_op)
assert_true(coverage_surface.import_funcs > 0)
assert_true(coverage_surface.import_tables > 0)
assert_true(coverage_surface.import_mems > 0)
assert_true(coverage_surface.import_globals > 0)
assert_true(coverage_surface.import_tags > 0)
assert_true(coverage_surface.passive_elems > 0)
assert_true(coverage_surface.declarative_elems > 0)
assert_true(coverage_surface.passive_datas > 0)
assert_true(coverage_surface.has_memory_init)
assert_true(coverage_surface.has_data_drop)
assert_true(coverage_surface.has_memory_copy)
assert_true(coverage_surface.has_memory_fill)
assert_true(coverage_surface.has_table_init)
assert_true(coverage_surface.has_elem_drop)
assert_true(coverage_surface.has_table_copy)
assert_true(coverage_surface.has_table_fill)
assert_true(gc_surface.struct_types > 0)
assert_true(gc_surface.array_types > 0)
assert_true(gc_surface.descriptor_types > 0)
assert_true(gc_surface.rec_groups > 0)
assert_true(gc_surface.typed_ref_globals > 0)
assert_true(gc_surface.typed_ref_tables > 0)
assert_true(gc_surface.typed_ref_locals > 0)
assert_true(gc_surface.exact_typed_ref_globals > 0)
assert_true(gc_surface.exact_typed_ref_locals > 0)
ignore(gc_surface.exact_typed_ref_results)
assert_true(has_gc_table)
assert_true(gc_surface.has_struct_new)
assert_true(gc_surface.has_struct_get)
assert_true(gc_surface.has_struct_set)
assert_true(gc_surface.has_array_new)
assert_true(gc_surface.has_array_get)
assert_true(gc_surface.has_array_set)
assert_true(gc_surface.has_array_len)
assert_true(gc_surface.has_string_const)
assert_true(gc_surface.has_string_new)
assert_true(gc_surface.has_string_encode)
assert_true(gc_surface.has_ref_get_desc)
assert_true(gc_surface.has_ref_test)
assert_true(gc_surface.has_ref_test_desc)
assert_true(gc_surface.has_ref_cast)
assert_true(gc_surface.has_ref_cast_desc_eq)
assert_true(gc_surface.has_ref_eq)
assert_true(gc_surface.has_ref_as_non_null)
assert_true(gc_surface.has_call_ref)
assert_true(gc_surface.has_br_on_null)
assert_true(gc_surface.has_br_on_non_null)
assert_true(gc_surface.has_br_on_cast)
assert_true(gc_surface.has_br_on_cast_fail)
assert_true(gc_surface.has_try_table)
assert_true(gc_surface.has_throw)
assert_true(gc_surface.has_throw_ref)
assert_true(has_shared_type_pool)
}
///|
test "gen_valid_feature_facts record imports sections exports and instruction families" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_import_sec(
ImportSec::new([
Import::new(
Name::new("env"),
Name::new("callee"),
ExternType::func(TypeIdx::new(0U)),
),
]),
)
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_table_sec(
TableSec::new([
Table::new(
TableType::new(
RefType::abs(AbsHeapType::func()),
Limits::i32(1U, Some(1U)),
),
None,
),
]),
)
.with_mem_sec(MemSec::new([MemType::new(Limits::i32(1U, Some(1U)))]))
.with_tag_sec(TagSec::new([TagType::new(TypeIdx::new(0U))]))
.with_global_sec(
GlobalSec::new([
Global::new(
GlobalType::new(ValType::v128(), false),
Expr::new([
Instruction::v128_const(
b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00',
b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00',
),
]),
),
]),
)
.with_export_sec(
ExportSec::new([
Export::new(Name::new("main"), ExternIdx::func(FuncIdx::new(1U))),
Export::new(Name::new("table"), ExternIdx::table(TableIdx::new(0U))),
Export::new(Name::new("memory"), ExternIdx::mem(MemIdx::new(0U))),
Export::new(Name::new("global"), ExternIdx::global(GlobalIdx::new(0U))),
Export::new(Name::new("tag"), ExternIdx::tag(TagIdx::new(0U))),
]),
)
.with_start_sec(StartSec::new(FuncIdx::new(1U)))
.with_elem_sec(
ElemSec::new([
Elem::new(
ElemMode::active(
TableIdx::new(0U),
Expr::new([Instruction::i32_const(I32(0))]),
),
ElemKind::funcs([FuncIdx::new(1U)]),
),
]),
)
.with_data_cnt_sec(DataCntSec::new(U32(1U)))
.with_data_sec(
DataSec::new([
Data::new(
DataMode::active(
MemIdx::new(0U),
Expr::new([Instruction::i32_const(I32(0))]),
),
b"fixture",
),
]),
)
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::call(FuncIdx::new(0U)),
Instruction::i32_const(I32(0)),
Instruction::call_indirect(TypeIdx::new(0U), TableIdx::new(0U)),
Instruction::block(BlockType::void_(), Expr::new([])),
Instruction::loop_(BlockType::void_(), Expr::new([])),
Instruction::i32_const(I32(1)),
Instruction::if_(
BlockType::void_(),
[Instruction::nop()],
Some([Instruction::nop()]),
),
]),
),
]),
)
let facts = gen_valid_feature_facts(mod, CoverageForced)
@debug.assert_eq(facts.mode, CoverageForced)
assert_true(facts.has_imports)
assert_true(facts.has_func_exports)
assert_true(facts.has_table_exports)
assert_true(facts.has_mem_exports)
assert_true(facts.has_global_exports)
assert_true(facts.has_tag_exports)
assert_true(facts.has_start)
assert_false(facts.lacks_start)
assert_true(facts.has_tables)
assert_true(facts.has_mems)
assert_true(facts.has_tags)
assert_true(facts.has_globals)
assert_true(facts.has_elems)
assert_true(facts.has_datas)
assert_true(facts.has_data_count)
assert_true(facts.uses_ref_types)
assert_true(facts.uses_v128)
assert_true(facts.has_call)
assert_true(facts.has_call_indirect)
assert_true(facts.has_branch_heavy_control)
}
///|
test "gen_valid_feature_facts records exact scalar and control opcode counts" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::i32_const(I32(1)),
Instruction::i32_const(I32(2)),
Instruction::i32_add(),
Instruction::drop(),
Instruction::i64_const(I64(3L)),
Instruction::i64_eqz(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_abs(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_neg(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_ceil(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_floor(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_trunc(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_nearest(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_sqrt(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_const(2.0),
Instruction::f32_eq(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_const(2.0),
Instruction::f32_sub(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_const(2.0),
Instruction::f32_mul(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_const(2.0),
Instruction::f32_div(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_const(2.0),
Instruction::f32_min(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_const(2.0),
Instruction::f32_max(),
Instruction::drop(),
Instruction::f32_const(1.0),
Instruction::f32_const(2.0),
Instruction::f32_copysign(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f64_abs(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f64_neg(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f64_ceil(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f64_floor(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f64_trunc(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f64_nearest(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f64_sqrt(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f64_const(2.0),
Instruction::f64_sub(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f64_const(2.0),
Instruction::f64_mul(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f64_const(2.0),
Instruction::f64_div(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f64_const(2.0),
Instruction::f64_min(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f64_const(2.0),
Instruction::f64_max(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f64_const(2.0),
Instruction::f64_copysign(),
Instruction::drop(),
Instruction::i64_const(I64(7L)),
Instruction::i32_wrap_i64(),
Instruction::drop(),
Instruction::f32_const(4.0),
Instruction::i32_trunc_f32s(),
Instruction::drop(),
Instruction::f32_const(4.0),
Instruction::i32_trunc_f32u(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::i32_trunc_f64s(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::i32_trunc_f64u(),
Instruction::drop(),
Instruction::i32_const(I32(7)),
Instruction::i64_extend_i32s(),
Instruction::drop(),
Instruction::i32_const(I32(7)),
Instruction::i64_extend_i32u(),
Instruction::drop(),
Instruction::f32_const(4.0),
Instruction::i64_trunc_f32s(),
Instruction::drop(),
Instruction::f32_const(4.0),
Instruction::i64_trunc_f32u(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::i64_trunc_f64s(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::i64_trunc_f64u(),
Instruction::drop(),
Instruction::i32_const(I32(7)),
Instruction::f32_convert_i32s(),
Instruction::drop(),
Instruction::i32_const(I32(7)),
Instruction::f32_convert_i32u(),
Instruction::drop(),
Instruction::i64_const(I64(7L)),
Instruction::f32_convert_i64s(),
Instruction::drop(),
Instruction::i64_const(I64(7L)),
Instruction::f32_convert_i64u(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::f32_demote_f64(),
Instruction::drop(),
Instruction::i32_const(I32(7)),
Instruction::f64_convert_i32s(),
Instruction::drop(),
Instruction::i32_const(I32(7)),
Instruction::f64_convert_i32u(),
Instruction::drop(),
Instruction::i64_const(I64(7L)),
Instruction::f64_convert_i64s(),
Instruction::drop(),
Instruction::i64_const(I64(7L)),
Instruction::f64_convert_i64u(),
Instruction::drop(),
Instruction::f32_const(4.0),
Instruction::f64_promote_f32(),
Instruction::drop(),
Instruction::i32_const(I32(0x7f)),
Instruction::i32_extend8s(),
Instruction::drop(),
Instruction::i32_const(I32(0x7fff)),
Instruction::i32_extend16s(),
Instruction::drop(),
Instruction::i64_const(I64(0x7fL)),
Instruction::i64_extend8s(),
Instruction::drop(),
Instruction::i64_const(I64(0x7fffL)),
Instruction::i64_extend16s(),
Instruction::drop(),
Instruction::i64_const(I64(0x7fffffffL)),
Instruction::i64_extend32s(),
Instruction::drop(),
Instruction::f64_const(4.0),
Instruction::i64_trunc_sat_f64u(),
Instruction::drop(),
Instruction::local_tee(LocalIdx::new(0U)),
Instruction::drop(),
Instruction::select(types=Some([ValType::i32()])),
Instruction::drop(),
Instruction::br_table([], LabelIdx::new(0U)),
Instruction::unreachable_(),
]),
),
]),
)
let facts = gen_valid_feature_facts(mod, CoverageForced)
let exact = facts.exact_opcode_counts
@debug.assert_eq(exact.i32_const, 10)
@debug.assert_eq(exact.i32_add, 1)
@debug.assert_eq(exact.i64_const, 9)
@debug.assert_eq(exact.i64_eqz, 1)
@debug.assert_eq(exact.f32_const, 26)
@debug.assert_eq(exact.f32_abs, 1)
@debug.assert_eq(exact.f32_neg, 1)
@debug.assert_eq(exact.f32_ceil, 1)
@debug.assert_eq(exact.f32_floor, 1)
@debug.assert_eq(exact.f32_trunc, 1)
@debug.assert_eq(exact.f32_nearest, 1)
@debug.assert_eq(exact.f32_sqrt, 1)
@debug.assert_eq(exact.f32_eq, 1)
@debug.assert_eq(exact.f32_sub, 1)
@debug.assert_eq(exact.f32_mul, 1)
@debug.assert_eq(exact.f32_div, 1)
@debug.assert_eq(exact.f32_min, 1)
@debug.assert_eq(exact.f32_max, 1)
@debug.assert_eq(exact.f32_copysign, 1)
@debug.assert_eq(exact.f64_const, 25)
@debug.assert_eq(exact.f64_abs, 1)
@debug.assert_eq(exact.f64_neg, 1)
@debug.assert_eq(exact.f64_ceil, 1)
@debug.assert_eq(exact.f64_floor, 1)
@debug.assert_eq(exact.f64_trunc, 1)
@debug.assert_eq(exact.f64_nearest, 1)
@debug.assert_eq(exact.f64_sqrt, 1)
@debug.assert_eq(exact.f64_sub, 1)
@debug.assert_eq(exact.f64_mul, 1)
@debug.assert_eq(exact.f64_div, 1)
@debug.assert_eq(exact.f64_min, 1)
@debug.assert_eq(exact.f64_max, 1)
@debug.assert_eq(exact.f64_copysign, 1)
@debug.assert_eq(exact.i32_wrap_i64, 1)
@debug.assert_eq(exact.i32_trunc_f32s, 1)
@debug.assert_eq(exact.i32_trunc_f32u, 1)
@debug.assert_eq(exact.i32_trunc_f64s, 1)
@debug.assert_eq(exact.i32_trunc_f64u, 1)
@debug.assert_eq(exact.i64_extend_i32s, 1)
@debug.assert_eq(exact.i64_extend_i32u, 1)
@debug.assert_eq(exact.i64_trunc_f32s, 1)
@debug.assert_eq(exact.i64_trunc_f32u, 1)
@debug.assert_eq(exact.i64_trunc_f64s, 1)
@debug.assert_eq(exact.i64_trunc_f64u, 1)
@debug.assert_eq(exact.f32_convert_i32s, 1)
@debug.assert_eq(exact.f32_convert_i32u, 1)
@debug.assert_eq(exact.f32_convert_i64s, 1)
@debug.assert_eq(exact.f32_convert_i64u, 1)
@debug.assert_eq(exact.f32_demote_f64, 1)
@debug.assert_eq(exact.f64_convert_i32s, 1)
@debug.assert_eq(exact.f64_convert_i32u, 1)
@debug.assert_eq(exact.f64_convert_i64s, 1)
@debug.assert_eq(exact.f64_convert_i64u, 1)
@debug.assert_eq(exact.f64_promote_f32, 1)
@debug.assert_eq(exact.i32_extend8s, 1)
@debug.assert_eq(exact.i32_extend16s, 1)
@debug.assert_eq(exact.i64_extend8s, 1)
@debug.assert_eq(exact.i64_extend16s, 1)
@debug.assert_eq(exact.i64_extend32s, 1)
@debug.assert_eq(exact.i64_trunc_sat_f64u, 1)
@debug.assert_eq(exact.local_tee, 1)
@debug.assert_eq(exact.typed_select, 1)
@debug.assert_eq(exact.br_table, 1)
@debug.assert_eq(exact.unreachable_opcode, 1)
let stats = GenValidFeatureStats::empty(CoverageForced)
.record(facts)
.record(facts)
@debug.assert_eq(stats.exact_opcode_counts.i32_const, 20)
@debug.assert_eq(stats.exact_opcode_counts.i64_trunc_sat_f64u, 2)
@debug.assert_eq(stats.exact_opcode_counts.local_tee, 2)
@debug.assert_eq(stats.exact_opcode_counts.br_table, 2)
}
///|
test "gen_valid_feature_facts records exact simd phase2 and phase3 opcode counts" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let memarg = MemArg::new(0U, None, 0UL)
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_mem_sec(MemSec::new([MemType::new(Limits::i32(1U, None))]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::i64x2_ge_s(),
Instruction::i32x4_all_true(),
Instruction::i8x16_sub_sat_u(),
Instruction::i32x4_min_s(),
Instruction::f64x2_nearest(),
Instruction::v128_load8x8s(memarg),
Instruction::v128_load64_zero(memarg),
Instruction::i16x8_extadd_pairwise_i8x16s(),
Instruction::i32x4_extend_low_i16x8s(),
Instruction::i64x2_extmul_high_i32x4u(),
Instruction::f64x2_promote_low_f32x4(),
]),
),
]),
)
let facts = gen_valid_feature_facts(mod, CoverageForced)
assert_true(facts.has_simd_phase2)
assert_true(facts.has_simd_phase3)
let exact = facts.exact_opcode_counts
@debug.assert_eq(exact.i64x2_ge_s, 1)
@debug.assert_eq(exact.i32x4_all_true, 1)
@debug.assert_eq(exact.i8x16_sub_sat_u, 1)
@debug.assert_eq(exact.i32x4_min_s, 1)
@debug.assert_eq(exact.f64x2_nearest, 1)
@debug.assert_eq(exact.v128_load8x8_s, 1)
@debug.assert_eq(exact.v128_load64_zero, 1)
@debug.assert_eq(exact.i16x8_extadd_pairwise_i8x16_s, 1)
@debug.assert_eq(exact.i32x4_extend_low_i16x8_s, 1)
@debug.assert_eq(exact.i64x2_extmul_high_i32x4_u, 1)
@debug.assert_eq(exact.f64x2_promote_low_f32x4, 1)
let stats = GenValidFeatureStats::empty(CoverageForced)
.record(facts)
.record(facts)
@debug.assert_eq(stats.exact_opcode_counts.i64x2_ge_s, 2)
@debug.assert_eq(stats.exact_opcode_counts.v128_load8x8_s, 2)
@debug.assert_eq(stats.exact_opcode_counts.i32x4_extend_low_i16x8_s, 2)
@debug.assert_eq(stats.exact_opcode_counts.i64x2_extmul_high_i32x4_u, 2)
@debug.assert_eq(stats.exact_opcode_counts.f64x2_promote_low_f32x4, 2)
}
///|
test "gen_valid coverage-forced records cross-feature ledger rows" {
let generated = gen_valid_module_result(
@splitmix.new(seed=0x5eedUL),
GenValidConfig::for_profile(CoverageForcedAllFeaturesProfile),
)
match generated {
Ok(result) => {
let facts = result.facts
assert_true(facts.has_gc_exception_combo)
assert_true(facts.has_gc_tail_call_combo)
assert_true(facts.has_shared_memory_atomics)
assert_true(facts.has_imports_start_exports)
let stats = GenValidFeatureStats::empty(CoverageForced).record(facts)
@debug.assert_eq(
validate_valid_feature_actual_count(stats, GcExceptionCombo),
1,
)
@debug.assert_eq(
validate_valid_feature_actual_count(stats, GcTailCallCombo),
1,
)
@debug.assert_eq(
validate_valid_feature_actual_count(stats, SharedMemoryAtomics),
1,
)
@debug.assert_eq(
validate_valid_feature_actual_count(stats, ImportsStartExports),
1,
)
}
Err(_) => fail("expected coverage-forced all-features module")
}
}
///|
test "validate_module reports typed ValidationError variant" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(CodeSec::new([Func::new(Locals::empty(), Expr::new([]))]))
.with_export_sec(
ExportSec::new([
Export::new(Name::new("dup"), ExternIdx::func(FuncIdx::new(0U))),
Export::new(Name::new("dup"), ExternIdx::func(FuncIdx::new(0U))),
]),
)
match validate_module(mod) {
Ok(_) => fail("expected duplicate export validation failure")
Err(err) => {
let diag = validation_error_diagnostic(err)
@debug.assert_eq(diag.func_idx, None)
assert_true(
validation_error_message(err).contains("duplicate export name"),
)
}
}
}
///|
test "validate_module requires data count section for memory.init and data.drop" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mem = MemType::new(Limits::i32(1, None))
let data = Data::new(DataMode::passive(), b"x")
let mem_init_mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_mem_sec(MemSec::new([mem]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::i32_const(I32(0)),
Instruction::i32_const(I32(0)),
Instruction::i32_const(I32(0)),
Instruction::memory_init(DataIdx::new(0), MemIdx::new(0)),
]),
),
]),
)
.with_data_sec(DataSec::new([data]))
match validate_module(mem_init_mod) {
Ok(_) => fail("expected memory.init without datacount to fail")
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
assert_true(
validation_error_message(err).contains("data count section required"),
)
}
}
let data_drop_mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([Instruction::data_drop(DataIdx::new(0))]),
),
]),
)
.with_data_sec(DataSec::new([data]))
match validate_module(data_drop_mod) {
Ok(_) => fail("expected data.drop without datacount to fail")
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
assert_true(
validation_error_message(err).contains("data count section required"),
)
}
}
}
///|
test "validate_defined_funcs_against_module reports all invalid changed bodies" {
let sig = RecType::new(
SubType::comp_type(CompType::func([], [ValType::i32()])),
)
let valid_func = Func::new(
Locals::empty(),
Expr::new([Instruction::i32_const(I32(1))]),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(
FuncSec::new([TypeIdx::new(0U), TypeIdx::new(0U), TypeIdx::new(0U)]),
)
.with_code_sec(CodeSec::new([valid_func, valid_func, valid_func]))
match
validate_defined_funcs_against_module(mod, [
(FuncIdx::new(0U), Func::new(Locals::empty(), Expr::new([]))),
(FuncIdx::new(1U), valid_func),
(
FuncIdx::new(2U),
Func::new(Locals::empty(), Expr::new([Instruction::f32_const(0.0)])),
),
]) {
Ok(errors) => {
@debug.assert_eq(errors.length(), 2)
let (first_idx, first_msg) = errors[0]
let (second_idx, second_msg) = errors[1]
@debug.assert_eq(first_idx, FuncIdx::new(0U))
assert_true(first_msg.contains("function body result stack underflow"))
@debug.assert_eq(second_idx, FuncIdx::new(2U))
assert_true(second_msg.contains("function body result type mismatch"))
}
Err(err) => fail("expected function-local diagnostics, got \{err}")
}
}
///|
test "validate_module reports imported-prefix func idx for missing data count" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mem = MemType::new(Limits::i32(1U, Some(1U)))
let data = Data::new(DataMode::passive(), b"x")
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_import_sec(
ImportSec::new([
Import::new(
Name::new("env"),
Name::new("f"),
ExternType::func(TypeIdx::new(0U)),
),
]),
)
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_mem_sec(MemSec::new([mem]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::i32_const(I32(0)),
Instruction::i32_const(I32(0)),
Instruction::i32_const(I32(0)),
Instruction::memory_init(DataIdx::new(0), MemIdx::new(0)),
]),
),
]),
)
.with_data_sec(DataSec::new([data]))
match validate_module(mod) {
Ok(_) => fail("expected memory.init without datacount to fail")
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(1U)))
assert_true(
validation_error_message(err).contains("data count section required"),
)
}
}
}
///|
test "validate_module rejects undeclared function reference in body" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::ref_func(FuncIdx::new(0U)),
Instruction::drop(),
]),
),
]),
)
match validate_module(mod) {
Ok(_) => fail("expected undeclared function reference to fail validation")
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
assert_true(
validation_error_message(err).contains("undeclared function reference"),
)
}
}
}
///|
test "validate_module reports imported-prefix func idx for undeclared ref.func in body" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_import_sec(
ImportSec::new([
Import::new(
Name::new("env"),
Name::new("f"),
ExternType::func(TypeIdx::new(0U)),
),
]),
)
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::ref_func(FuncIdx::new(1U)),
Instruction::drop(),
]),
),
]),
)
match validate_module(mod) {
Ok(_) => fail("expected undeclared function reference to fail validation")
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(1U)))
assert_true(
validation_error_message(err).contains("undeclared function reference"),
)
}
}
}
///|
test "validate_module does not treat start as a ref.func declaration source" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_start_sec(StartSec::new(FuncIdx::new(0U)))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::ref_func(FuncIdx::new(0U)),
Instruction::drop(),
]),
),
]),
)
match validate_module(mod) {
Ok(_) =>
fail(
"expected start entry alone to be insufficient for ref.func declaration",
)
Err(err) =>
assert_true(
validation_error_message(err).contains("undeclared function reference"),
)
}
}
///|
test "validate_module accepts ref.func body when function is exported" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::ref_func(FuncIdx::new(0U)),
Instruction::drop(),
]),
),
]),
)
.with_export_sec(
ExportSec::new([
Export::new(Name::new("f"), ExternIdx::func(FuncIdx::new(0U))),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected exported function to count as declared for ref.func: \{validation_error_message(err)}",
)
}
}
///|
test "validate_const_expr rejects ref.func singleton type as rec-group member" {
let grouped = RecType::group([
SubType::comp_type(CompType::func([], [])),
SubType::comp_type(CompType::func([], [])),
])
let singleton = RecType::new(SubType::comp_type(CompType::func([], [])))
let type_sec = TypeSec::new([grouped, singleton])
let env = match validate_typesec(Some(type_sec), Env::new()) {
Ok(env) => env
Err(e) => fail("expected type section to validate: \{e}")
}
let env = match
validate_funcsec(Some(FuncSec::new([TypeIdx::new(2U)])), env) {
Ok(env) => env
Err(e) => fail("expected function section to validate: \{e}")
}
let want = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0U))),
)
let expr = Expr::new([Instruction::ref_func(FuncIdx::new(0U))])
let state = TcState::{
env,
stack: [],
reachable: true,
polymorphic: false,
escape: NoTcEscape,
reachable_escape_depths: [],
initialized_locals: [],
}
let typed = match Typecheck::typecheck(expr, state) {
Ok(state) => state
Err(e) => fail("expected ref.func typecheck success: \{e}")
}
@debug.assert_eq(typed.stack, [
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(2U)), exact=true),
),
])
match validate_const_expr(env, expr, want) {
Ok(_) => fail("expected recursive-group member mismatch")
Err(e) => assert_true(e.contains("type mismatch"))
}
}
///|
test "validate_module treats global initializer ref.func as declaration source" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_global_sec(
GlobalSec::new([
Global::new(
GlobalType::new(ValType::funcref(), false),
Expr::new([Instruction::ref_func(FuncIdx::new(0U))]),
),
]),
)
.with_code_sec(CodeSec::new([Func::new(Locals::empty(), Expr::new([]))]))
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected global initializer ref.func declaration source to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module treats table initializer ref.func as declaration source" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let table_type = TableType::new(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
Limits::i32(1, None),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_table_sec(
TableSec::new([
Table::new(
table_type,
Some(Expr::new([Instruction::ref_func(FuncIdx::new(0U))])),
),
]),
)
.with_code_sec(CodeSec::new([Func::new(Locals::empty(), Expr::new([]))]))
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected table initializer ref.func declaration source to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module treats elem expression ref.func as declaration source" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let elem = Elem::new(
ElemMode::passive(),
ElemKind::typed_exprs(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
[Expr::new([Instruction::ref_func(FuncIdx::new(0U))])],
),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_elem_sec(ElemSec::new([elem]))
.with_code_sec(CodeSec::new([Func::new(Locals::empty(), Expr::new([]))]))
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected elem expression ref.func declaration source to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module accepts ref.func in elem expression when elem.funcs declares the function" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let decls = Elem::new(
ElemMode::declarative(),
ElemKind::funcs([FuncIdx::new(0U)]),
)
let elem_expr = Elem::new(
ElemMode::passive(),
ElemKind::typed_exprs(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
[Expr::new([Instruction::ref_func(FuncIdx::new(0U))])],
),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_elem_sec(ElemSec::new([decls, elem_expr]))
.with_code_sec(CodeSec::new([Func::new(Locals::empty(), Expr::new([]))]))
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected elem expression ref.func to validate when declared by elem.funcs: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module accepts ref.func in global initializer when function is exported" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_global_sec(
GlobalSec::new([
Global::new(
GlobalType::new(ValType::funcref(), false),
Expr::new([Instruction::ref_func(FuncIdx::new(0U))]),
),
]),
)
.with_export_sec(
ExportSec::new([
Export::new(Name::new("f"), ExternIdx::func(FuncIdx::new(0U))),
]),
)
.with_code_sec(CodeSec::new([Func::new(Locals::empty(), Expr::new([]))]))
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected exported function declaration source to allow global initializer ref.func: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module rejects mixed declared and undeclared ref.func uses in one body" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U), TypeIdx::new(0U)]))
.with_export_sec(
ExportSec::new([
Export::new(Name::new("declared"), ExternIdx::func(FuncIdx::new(0U))),
]),
)
.with_code_sec(
CodeSec::new([
Func::new(Locals::empty(), Expr::new([])),
Func::new(
Locals::empty(),
Expr::new([
Instruction::ref_func(FuncIdx::new(0U)),
Instruction::drop(),
Instruction::ref_func(FuncIdx::new(1U)),
Instruction::drop(),
]),
),
]),
)
match validate_module(mod) {
Ok(_) =>
fail(
"expected mixed declared/undeclared ref.func uses to fail validation",
)
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(1U)))
assert_true(
validation_error_message(err).contains("undeclared function reference"),
)
}
}
}
///|
test "validate_module accepts global initializer global.get from earlier immutable module global" {
let mod = Module::new().with_global_sec(
GlobalSec::new([
Global::new(
GlobalType::new(ValType::i32(), false),
Expr::new([Instruction::i32_const(I32(7))]),
),
Global::new(
GlobalType::new(ValType::i32(), false),
Expr::new([Instruction::global_get(GlobalIdx::new(0U))]),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected extended-const global.get from earlier immutable global to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module accepts global initializer global.get from imported immutable global" {
let mod = Module::new()
.with_import_sec(
ImportSec::new([
Import::new(
Name::new("env"),
Name::new("g"),
ExternType::global(GlobalType::new(ValType::i32(), false)),
),
]),
)
.with_global_sec(
GlobalSec::new([
Global::new(
GlobalType::new(ValType::i32(), false),
Expr::new([Instruction::global_get(GlobalIdx::new(0U))]),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected imported immutable global.get in const expression to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module rejects non-extended unary arithmetic in elem offsets" {
let mod = Module::new()
.with_table_sec(
TableSec::new([
Table::new(
TableType::new(
RefType::abs(AbsHeapType::func()),
Limits::i32(1U, None),
),
None,
),
]),
)
.with_elem_sec(
ElemSec::new([
Elem::new(
ElemMode::active(
TableIdx::new(0U),
Expr::new([Instruction::i32_const(I32(0)), Instruction::i32_ctz()]),
),
ElemKind::funcs([]),
),
]),
)
match validate_module(mod) {
Ok(_) => fail("expected i32.ctz element offset to be rejected")
Err(err) =>
assert_true(
validation_error_message(err).contains(
"initializer expression is not constant",
),
)
}
}
///|
test "validate_module accepts arithmetic in const expressions" {
let mod = Module::new()
.with_mem_sec(MemSec::new([MemType::new(Limits::i32(1, None))]))
.with_global_sec(
GlobalSec::new([
Global::new(
GlobalType::new(ValType::i32(), false),
Expr::new([
Instruction::i32_const(I32(-877605212)),
Instruction::i32_const(I32(1580649160)),
Instruction::i32_sub(),
]),
),
]),
)
.with_data_sec(
DataSec::new([
Data::new(
DataMode::active(
MemIdx::new(0U),
Expr::new([
Instruction::i32_const(I32(1)),
Instruction::i32_const(I32(2)),
Instruction::i32_add(),
]),
),
b"x",
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected arithmetic const expressions to validate: \{validation_error_message(err)}",
)
}
}
///|
fn validate_test_const_expr_modules_for_numeric_body(
body : Array[Instruction],
) -> Array[(String, Module)] {
let i31_ref = RefType::new(true, HeapType::abs(AbsHeapType::i31()))
let ref_body = body.copy()
ref_body.push(Instruction::ref_i31())
[
(
"global initializer",
Module::new().with_global_sec(
GlobalSec::new([
Global::new(
GlobalType::new(ValType::i32(), false),
Expr::new(body.copy()),
),
]),
),
),
(
"data offset",
Module::new()
.with_mem_sec(MemSec::new([MemType::new(Limits::i32(1, None))]))
.with_data_sec(
DataSec::new([
Data::new(
DataMode::active(MemIdx::new(0U), Expr::new(body.copy())),
b"x",
),
]),
),
),
(
"element offset",
Module::new()
.with_table_sec(
TableSec::new([
Table::new(
TableType::new(
RefType::abs(AbsHeapType::func()),
Limits::i32(1U, None),
),
None,
),
]),
)
.with_elem_sec(
ElemSec::new([
Elem::new(
ElemMode::active(TableIdx::new(0U), Expr::new(body.copy())),
ElemKind::funcs([]),
),
]),
),
),
(
"table initializer",
Module::new().with_table_sec(
TableSec::new([
Table::new(
TableType::new(i31_ref, Limits::i32(1U, None)),
Some(Expr::new(ref_body.copy())),
),
]),
),
),
(
"element initializer",
Module::new().with_elem_sec(
ElemSec::new([
Elem::new(
ElemMode::passive(),
ElemKind::typed_exprs(i31_ref, [Expr::new(ref_body)]),
),
]),
),
),
]
}
///|
test "validate_module rejects non-spec numeric instructions in every const-expression context" {
let cases : Array[(String, Array[Instruction])] = [
(
"integer division",
[
Instruction::i32_const(I32(4)),
Instruction::i32_const(I32(2)),
Instruction::i32_div_s(),
],
),
(
"integer remainder",
[
Instruction::i32_const(I32(5)),
Instruction::i32_const(I32(2)),
Instruction::i32_rem_u(),
],
),
(
"bitwise and",
[
Instruction::i32_const(I32(3)),
Instruction::i32_const(I32(1)),
Instruction::i32_and(),
],
),
(
"bitwise or",
[
Instruction::i32_const(I32(2)),
Instruction::i32_const(I32(1)),
Instruction::i32_or(),
],
),
(
"bitwise xor",
[
Instruction::i32_const(I32(3)),
Instruction::i32_const(I32(1)),
Instruction::i32_xor(),
],
),
(
"shift",
[
Instruction::i32_const(I32(4)),
Instruction::i32_const(I32(1)),
Instruction::i32_shl(),
],
),
(
"rotate",
[
Instruction::i32_const(I32(4)),
Instruction::i32_const(I32(1)),
Instruction::i32_rotr(),
],
),
(
"comparison",
[
Instruction::i32_const(I32(4)),
Instruction::i32_const(I32(2)),
Instruction::i32_eq(),
],
),
(
"float arithmetic",
[
Instruction::f32_const(@lib.F32::new(1.0)),
Instruction::f32_const(@lib.F32::new(2.0)),
Instruction::f32_add(),
Instruction::i32_reinterpret_f32(),
],
),
("bit count", [Instruction::i32_const(I32(4)), Instruction::i32_clz()]),
(
"unsupported conversion",
[Instruction::i64_const(I64(4L)), Instruction::i32_wrap_i64()],
),
]
for case in cases {
for fixture in validate_test_const_expr_modules_for_numeric_body(case.1) {
match validate_module(fixture.1) {
Ok(_) =>
fail(
"expected \{case.0} to be rejected in \{fixture.0} constant expression",
)
Err(err) =>
assert_true(
validation_error_message(err).contains(
"initializer expression is not constant",
),
msg="unexpected \{case.0} / \{fixture.0} error: \{validation_error_message(err)}",
)
}
}
}
}
///|
test "validate_module rejects global initializer global.get from imported mutable global" {
let mod = Module::new()
.with_import_sec(
ImportSec::new([
Import::new(
Name::new("env"),
Name::new("g"),
ExternType::global(GlobalType::new(ValType::i32(), true)),
),
]),
)
.with_global_sec(
GlobalSec::new([
Global::new(
GlobalType::new(ValType::i32(), false),
Expr::new([Instruction::global_get(GlobalIdx::new(0U))]),
),
]),
)
match validate_module(mod) {
Ok(_) => fail("expected mutable imported global.get in const expr to fail")
Err(err) => assert_true(validation_error_message(err).contains("constant"))
}
}
///|
test "validate_module rejects global initializer global.get from earlier mutable module global" {
let mod = Module::new().with_global_sec(
GlobalSec::new([
Global::new(
GlobalType::new(ValType::i32(), true),
Expr::new([Instruction::i32_const(I32(7))]),
),
Global::new(
GlobalType::new(ValType::i32(), false),
Expr::new([Instruction::global_get(GlobalIdx::new(0U))]),
),
]),
)
match validate_module(mod) {
Ok(_) =>
fail("expected mutable module global.get in const expression to fail")
Err(err) => assert_true(validation_error_message(err).contains("constant"))
}
}
///|
test "validate_module accepts table initializer global.get from imported immutable ref global" {
let table_type = TableType::new(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
Limits::i32(2, None),
)
let mod = Module::new()
.with_import_sec(
ImportSec::new([
Import::new(
Name::new("env"),
Name::new("g"),
ExternType::global(
GlobalType::new(
ValType::ref_type(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
),
false,
),
),
),
]),
)
.with_table_sec(
TableSec::new([
Table::new(
table_type,
Some(Expr::new([Instruction::global_get(GlobalIdx::new(0U))])),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected table initializer global.get from immutable imported global to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module rejects data offset with non-constant instruction sequence" {
let mod = Module::new()
.with_mem_sec(MemSec::new([MemType::new(Limits::i32(1, None))]))
.with_data_sec(
DataSec::new([
Data::new(
DataMode::active(
MemIdx::new(0U),
Expr::new([Instruction::i32_const(I32(1)), Instruction::drop()]),
),
b"x",
),
]),
)
match validate_module(mod) {
Ok(_) => fail("expected non-constant data offset expression to fail")
Err(err) => assert_true(validation_error_message(err).contains("constant"))
}
}
///|
test "validate_module accepts ref.null in const global initializer" {
let mod = Module::new().with_global_sec(
GlobalSec::new([
Global::new(
GlobalType::new(ValType::funcref(), false),
Expr::new([Instruction::ref_null(HeapType::abs(AbsHeapType::func()))]),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected ref.null const global initializer to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module accepts descriptor-bearing struct.new_default_desc const initializers" {
let descriptor_group = RecType::group([
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(descriptor=Some(TypeIdx::new(1U))),
),
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::new(0U))),
),
])
let mod = Module::new()
.with_type_sec(TypeSec::new([descriptor_group]))
.with_import_sec(
ImportSec::new([
Import::new(
Name::new("env"),
Name::new("desc"),
ExternType::global(
GlobalType::new(
ValType::ref_type(
RefType::new(true, HeapType::new(TypeIdx::new(1U)), exact=true),
),
false,
),
),
),
]),
)
.with_global_sec(
GlobalSec::new([
Global::new(
GlobalType::new(
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0U)), exact=true),
),
false,
),
Expr::new([
Instruction::global_get(GlobalIdx::new(0U)),
Instruction::struct_new_default_desc(TypeIdx::new(0U)),
]),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected descriptor-bearing const initializer to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module accepts table initializer array.new_fixed const expression" {
let array_idx = TypeIdx::new(0U)
let array_ref = RefType::new(true, HeapType::new(array_idx))
let mod = Module::new()
.with_type_sec(
TypeSec::new([
RecType::new(
SubType::comp_type(
CompType::array(
FieldType::new(StorageType::pack_type(PackType::i16()), Const),
),
),
),
]),
)
.with_table_sec(
TableSec::new([
Table::new(
TableType::new(array_ref, Limits::i32(0U, None)),
Some(
Expr::new([
Instruction::i32_const(I32(1)),
Instruction::i32_const(I32(2)),
Instruction::i32_const(I32(3)),
Instruction::array_new_fixed(array_idx, U32::new(3U)),
]),
),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected table initializer array.new_fixed const expression to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module accepts elem payload array.new_default const expression" {
let array_idx = TypeIdx::new(0U)
let array_ref = RefType::new(true, HeapType::new(array_idx))
let mod = Module::new()
.with_type_sec(
TypeSec::new([
RecType::new(
SubType::comp_type(
CompType::array(
FieldType::new(StorageType::pack_type(PackType::i8()), Var),
),
),
),
]),
)
.with_elem_sec(
ElemSec::new([
Elem::new(
ElemMode::passive(),
ElemKind::typed_exprs(array_ref, [
Expr::new([
Instruction::i32_const(I32(230)),
Instruction::array_new_default(array_idx),
]),
]),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected elem payload array.new_default const expression to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module accepts elem payload array.new const expression" {
let array_idx = TypeIdx::new(0U)
let array_ref = RefType::new(true, HeapType::new(array_idx))
let exn_ref = RefType::abs(AbsHeapType::exn())
let mod = Module::new()
.with_type_sec(
TypeSec::new([
RecType::new(
SubType::comp_type(
CompType::array(
FieldType::new(
StorageType::val_type(ValType::ref_type(exn_ref)),
Var,
),
),
),
),
]),
)
.with_elem_sec(
ElemSec::new([
Elem::new(
ElemMode::passive(),
ElemKind::typed_exprs(array_ref, [
Expr::new([
Instruction::ref_null(HeapType::abs(AbsHeapType::exn())),
Instruction::i32_const(I32(2)),
Instruction::array_new(array_idx),
]),
]),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected elem payload array.new const expression to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module accepts zero-length v128 array.new_fixed table initializer" {
let array_idx = TypeIdx::new(0U)
let array_ref = RefType::new(true, HeapType::new(array_idx))
let mod = Module::new()
.with_type_sec(
TypeSec::new([
RecType::new(
SubType::comp_type(
CompType::array(
FieldType::new(StorageType::val_type(ValType::v128()), Var),
),
),
),
]),
)
.with_table_sec(
TableSec::new([
Table::new(
TableType::new(array_ref, Limits::i32(1U, None)),
Some(
Expr::new([Instruction::array_new_fixed(array_idx, U32::new(0U))]),
),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected zero-length v128 array.new_fixed table initializer to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module rejects descriptor clauses on non-struct types" {
let bad_group = RecType::new(
SubType::comp_type(
CompType::func([], []),
metadata=TypeMetadata::new(descriptor=Some(TypeIdx::new(0U))),
),
)
let mod = Module::new().with_type_sec(TypeSec::new([bad_group]))
match validate_module(mod) {
Ok(_) => fail("expected non-struct descriptor metadata to fail validation")
Err(err) =>
assert_true(
validation_error_message(err).contains("only appear on struct"),
)
}
}
///|
test "validate_module rejects describes targets that are not previously defined" {
let bad_group = RecType::group([
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::new(1U))),
),
SubType::comp_type(CompType::struct_([])),
])
let mod = Module::new().with_type_sec(TypeSec::new([bad_group]))
match validate_module(mod) {
Ok(_) => fail("expected forward describes target to fail validation")
Err(err) =>
assert_true(validation_error_message(err).contains("previously defined"))
}
}
///|
test "validate_module accepts descriptor metadata that uses RecIdx targets" {
let descriptor_group = RecType::group([
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(descriptor=Some(TypeIdx::rec(1))),
),
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::rec(0))),
),
])
let mod = Module::new().with_type_sec(TypeSec::new([descriptor_group]))
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected RecIdx descriptor metadata to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module rejects RecIdx descriptor targets outside the current group" {
let bad_group = RecType::group([
SubType::comp_type(CompType::struct_([])),
SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::rec(2))),
),
])
let mod = Module::new().with_type_sec(TypeSec::new([bad_group]))
match validate_module(mod) {
Ok(_) =>
fail("expected out-of-range RecIdx describes target to fail validation")
Err(err) =>
assert_true(
validation_error_message(err).contains("must be a valid type"),
)
}
}
///|
test "validate_module rejects tail loop that leaves function result missing" {
let sig = RecType::new(
SubType::comp_type(CompType::func([ValType::i32()], [ValType::i32()])),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([Instruction::loop_(BlockType::void_(), Expr::new([]))]),
),
]),
)
match validate_module(mod) {
Ok(_) =>
fail(
"expected missing i32 function result after tail loop to fail validation",
)
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
let msg = validation_error_message(err)
assert_true(msg.contains("function body result stack underflow"))
assert_true(msg.contains("expected stack="))
assert_true(msg.contains("actual stack="))
assert_true(msg.contains("I32"))
}
}
}
///|
test "validate_module reports end-of-body underflow with stack shape" {
let sig = RecType::new(
SubType::comp_type(CompType::func([], [ValType::i32()])),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(CodeSec::new([Func::new(Locals::empty(), Expr::new([]))]))
match validate_module(mod) {
Ok(_) => fail("expected missing function result to fail validation")
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
let msg = validation_error_message(err)
assert_true(msg.contains("function body result stack underflow"))
assert_true(msg.contains("expected stack="))
assert_true(msg.contains("actual stack="))
assert_true(msg.contains("I32"))
}
}
}
///|
test "validate_module rejects outer block result mismatch reachable through an inner block-local branch" {
let struct_type = RecType::new(SubType::comp_type(CompType::struct_([])))
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let non_null_struct = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0U))),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([struct_type, sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(1U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::block(
BlockType::val_type(ValType::i32()),
Expr::new([
Instruction::block(
BlockType::val_type(non_null_struct),
Expr::new([
Instruction::i32_const(I32(1)),
Instruction::if_(
BlockType::void_(),
[
Instruction::struct_new_default(TypeIdx::new(0U)),
Instruction::br(LabelIdx::new(1U)),
],
Some([]),
),
Instruction::i32_const(I32(0)),
Instruction::br(LabelIdx::new(1U)),
]),
),
]),
),
Instruction::drop(),
]),
),
]),
)
match validate_module(mod) {
Ok(_) =>
fail(
"expected outer i32 block to reject an inner branch-completed reference result",
)
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
assert_true(validation_error_message(err).contains("type mismatch"))
}
}
}
///|
test "validate_module accepts initialized non-defaultable body locals" {
let struct_type = RecType::new(SubType::comp_type(CompType::struct_([])))
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let non_null_struct = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0U))),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([struct_type, sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(1U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::from_types([non_null_struct]),
Expr::new([
Instruction::struct_new_default(TypeIdx::new(0U)),
Instruction::local_set(LocalIdx::new(0U)),
Instruction::local_get(LocalIdx::new(0U)),
Instruction::drop(),
]),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) => fail(validation_error_message(err))
}
}
///|
test "validate_module rejects uninitialized non-defaultable body local reads" {
let struct_type = RecType::new(SubType::comp_type(CompType::struct_([])))
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let non_null_struct = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0U))),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([struct_type, sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(1U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::from_types([non_null_struct]),
Expr::new([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::drop(),
]),
),
]),
)
match validate_module(mod) {
Ok(_) => fail("expected uninitialized non-defaultable local read to fail")
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
assert_true(validation_error_message(err).contains("uninitialized local"))
}
}
}
///|
test "validate_module rejects direct block-return non-defaultable unreachable-tail local get" {
let parent_type = RecType::new(SubType::new(false, [], CompType::struct_([])))
let child_type = RecType::new(
SubType::new(false, [TypeIdx::new(0U)], CompType::struct_([])),
)
let child_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(1U))),
)
let sig = RecType::new(SubType::comp_type(CompType::func([child_ref], [])))
let mod = Module::new()
.with_type_sec(TypeSec::new([parent_type, child_type, sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(2U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::from_types([child_ref]),
Expr::new([
Instruction::block(
BlockType::void_(),
Expr::new([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::local_set(LocalIdx::new(1U)),
Instruction::return_(),
]),
),
Instruction::local_get(LocalIdx::new(1U)),
Instruction::drop(),
]),
),
]),
)
match validate_module(mod) {
Ok(_) =>
fail(
"expected unreachable-tail non-defaultable local read after direct block return to fail",
)
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
assert_true(validation_error_message(err).contains("uninitialized local"))
}
}
}
///|
test "validate_module reports wrong end-of-body result type with stack shape" {
let sig = RecType::new(
SubType::comp_type(CompType::func([], [ValType::i32()])),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(Locals::empty(), Expr::new([Instruction::f32_const(0.0)])),
]),
)
match validate_module(mod) {
Ok(_) => fail("expected wrong function result type to fail validation")
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
let msg = validation_error_message(err)
assert_true(msg.contains("function body result type mismatch"))
assert_true(msg.contains("expected stack="))
assert_true(msg.contains("actual stack="))
assert_true(msg.contains("I32"))
assert_true(msg.contains("F32"))
}
}
}
///|
test "validate_module reports end-of-body extra values with stack shape" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(Locals::empty(), Expr::new([Instruction::i32_const(I32(1))])),
]),
)
match validate_module(mod) {
Ok(_) => fail("expected extra stack values to fail validation")
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
let msg = validation_error_message(err)
assert_true(msg.contains("function body leaves extra values on stack"))
assert_true(msg.contains("expected stack="))
assert_true(msg.contains("actual stack="))
assert_true(msg.contains("I32"))
}
}
}
///|
test "validate_module rejects typed if branch result mismatch hidden behind terminal block after br_if" {
let sig = RecType::new(
SubType::comp_type(CompType::func([ValType::i32()], [ValType::i64()])),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::if_(
BlockType::val_type(ValType::i64()),
[
Instruction::block(
BlockType::void_(),
Expr::new([
Instruction::loop_(
BlockType::void_(),
Expr::new([
Instruction::i32_const(I32(0)),
Instruction::br_if(LabelIdx::new(1U)),
]),
),
Instruction::unreachable_(),
]),
),
Instruction::i32_const(I32(1)),
],
Some([Instruction::i64_const(@lib.I64::new(0L))]),
),
]),
),
]),
)
match validate_module(mod) {
Ok(_) =>
fail(
"expected typed if branch result mismatch after conditional block escape to fail validation",
)
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
let msg = validation_error_message(err)
assert_true(msg.contains("type mismatch"), msg~)
}
}
}
///|
test "validate_module rejects concrete stack junk after return inside block" {
let sig = RecType::new(SubType::comp_type(CompType::func([], [])))
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::block(
BlockType::void_(),
Expr::new([Instruction::return_(), Instruction::i32_const(I32(1))]),
),
]),
),
]),
)
match validate_module(mod) {
Ok(_) =>
fail("expected block to reject concrete stack values left after return")
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
assert_true(validation_error_message(err).length() > 0)
}
}
}
///|
test "validate_module rejects wrong concrete loop result after infinite inner loop" {
let sig = RecType::new(
SubType::comp_type(CompType::func([], [ValType::i32()])),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::loop_(
BlockType::val_type(ValType::i32()),
Expr::new([
Instruction::loop_(BlockType::void_(), Expr::new([])),
Instruction::f32_const(0.0),
]),
),
]),
),
]),
)
match validate_module(mod) {
Ok(_) =>
fail(
"expected loop to reject wrong concrete result left after an infinite inner loop",
)
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
assert_true(validation_error_message(err).length() > 0)
}
}
}
///|
test "validate_module rejects outer if result missing after inner current-label branches" {
let sig = RecType::new(
SubType::comp_type(CompType::func([ValType::i32()], [ValType::i32()])),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::block(
BlockType::void_(),
Expr::new([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::i32_const(I32(0)),
Instruction::i32_eq(),
Instruction::if_(
BlockType::val_type(ValType::i32()),
[Instruction::i32_const(I32(1000))],
Some([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::i32_const(I32(999)),
Instruction::i32_eq(),
Instruction::if_(
BlockType::void_(),
[Instruction::br(LabelIdx::new(0))],
Some([Instruction::br(LabelIdx::new(0))]),
),
]),
),
Instruction::drop(),
]),
),
Instruction::i32_const(I32(0)),
]),
),
]),
)
match validate_module(mod) {
Ok(_) =>
fail(
"expected missing outer if result after inner current-label branches to fail validation",
)
Err(err) => {
@debug.assert_eq(validation_error_func_idx(err), Some(FuncIdx::new(0U)))
assert_true(validation_error_message(err).length() > 0)
}
}
}
///|
test "validate_module accepts outer if when nested outer-label branches skip the merge" {
let sig = RecType::new(
SubType::comp_type(CompType::func([ValType::i32()], [ValType::i32()])),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::block(
BlockType::void_(),
Expr::new([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::if_(
BlockType::val_type(ValType::i32()),
[Instruction::i32_const(I32(1000))],
Some([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::i32_const(I32(999)),
Instruction::i32_eq(),
Instruction::if_(
BlockType::void_(),
[Instruction::br(LabelIdx::new(2))],
Some([Instruction::br(LabelIdx::new(2))]),
),
// Explicitly mark this arm polymorphic after the child frame.
Instruction::unreachable_(),
]),
),
Instruction::drop(),
]),
),
Instruction::i32_const(I32(0)),
]),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected nested outer-label branches to skip the outer if merge cleanly: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module accepts nested result if ladder when deeper branches skip later returns" {
let sig = RecType::new(
SubType::comp_type(CompType::func([ValType::i32()], [ValType::i64()])),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::block(
BlockType::void_(),
Expr::new([
Instruction::block(
BlockType::void_(),
Expr::new([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::if_(
BlockType::val_type(ValType::i64()),
[
Instruction::local_get(LocalIdx::new(0U)),
Instruction::drop(),
Instruction::br(LabelIdx::new(1U)),
],
Some([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::if_(
BlockType::val_type(ValType::i64()),
[
Instruction::local_get(LocalIdx::new(0U)),
Instruction::drop(),
Instruction::br(LabelIdx::new(2U)),
],
Some([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::if_(
BlockType::val_type(ValType::i64()),
[
Instruction::local_get(LocalIdx::new(0U)),
Instruction::drop(),
Instruction::br(LabelIdx::new(4U)),
],
Some([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::if_(
BlockType::val_type(ValType::i64()),
[
Instruction::local_get(LocalIdx::new(0U)),
Instruction::drop(),
Instruction::br(LabelIdx::new(5U)),
],
Some([
Instruction::i64_const(I64(4294967296L)),
]),
),
]),
),
]),
),
]),
),
Instruction::return_(),
]),
),
Instruction::i64_const(I64(0L)),
Instruction::return_(),
]),
),
Instruction::i64_const(I64(1L)),
]),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected nested branch ladder to skip later returns cleanly: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module accepts result if that can either return or escape to later continuation" {
let ret_sig = RecType::new(
SubType::comp_type(
CompType::func([ValType::i32(), ValType::i32()], [ValType::i32()]),
),
)
let join_sig = RecType::new(
SubType::comp_type(
CompType::func([ValType::i32(), ValType::i32()], [ValType::i32()]),
),
)
let mod = Module::new()
.with_type_sec(TypeSec::new([ret_sig, join_sig]))
.with_func_sec(FuncSec::new([TypeIdx::new(0U), TypeIdx::new(1U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::local_get(LocalIdx::new(1U)),
Instruction::i32_add(),
]),
),
Func::new(
Locals::from_types([ValType::i32()]),
Expr::new([
Instruction::block(
BlockType::void_(),
Expr::new([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::if_(
BlockType::val_type(ValType::i32()),
[Instruction::i32_const(I32(0))],
Some([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::if_(
BlockType::val_type(ValType::i32()),
[
Instruction::i32_const(I32(7)),
Instruction::local_set(LocalIdx::new(2U)),
Instruction::br(LabelIdx::new(2U)),
],
Some([
Instruction::i32_const(I32(8)),
Instruction::local_set(LocalIdx::new(2U)),
Instruction::br(LabelIdx::new(2U)),
]),
),
]),
),
Instruction::return_(),
]),
),
Instruction::local_get(LocalIdx::new(2U)),
Instruction::local_get(LocalIdx::new(1U)),
Instruction::call(FuncIdx::new(0U)),
]),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected mixed return-or-escape if fixture to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module accepts result if that can either branch to current block or return" {
let mod = Module::new()
.with_type_sec(
TypeSec::new([
RecType::new(SubType::comp_type(CompType::func([], [ValType::i32()]))),
]),
)
.with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::block(
BlockType::val_type(ValType::i32()),
Expr::new([
Instruction::i32_const(I32(1)),
Instruction::if_(
BlockType::val_type(ValType::i32()),
[
Instruction::i32_const(I32(7)),
Instruction::br(LabelIdx::new(1U)),
],
Some([Instruction::i32_const(I32(8)), Instruction::return_()]),
),
]),
),
]),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected branch-or-return result if fixture to validate: \{validation_error_message(err)}",
)
}
}
// ============ NumType Tests ============
///|
test "NumType validates successfully" {
let env = empty_env()
@debug.assert_eq(Validate::validate(NumType::f32(), env), Ok(()))
@debug.assert_eq(Validate::validate(NumType::f64(), env), Ok(()))
@debug.assert_eq(Validate::validate(NumType::i32(), env), Ok(()))
@debug.assert_eq(Validate::validate(NumType::i64(), env), Ok(()))
}
// ============ AbsHeapType Tests ============
///|
test "AbsHeapType validates successfully" {
let env = empty_env()
@debug.assert_eq(Validate::validate(AbsHeapType::exn(), env), Ok(()))
@debug.assert_eq(Validate::validate(AbsHeapType::array(), env), Ok(()))
@debug.assert_eq(Validate::validate(AbsHeapType::struct_(), env), Ok(()))
@debug.assert_eq(Validate::validate(AbsHeapType::i31(), env), Ok(()))
@debug.assert_eq(Validate::validate(AbsHeapType::eq(), env), Ok(()))
@debug.assert_eq(Validate::validate(AbsHeapType::any(), env), Ok(()))
@debug.assert_eq(Validate::validate(AbsHeapType::extern_(), env), Ok(()))
@debug.assert_eq(Validate::validate(AbsHeapType::func(), env), Ok(()))
@debug.assert_eq(Validate::validate(AbsHeapType::none(), env), Ok(()))
@debug.assert_eq(Validate::validate(AbsHeapType::no_extern(), env), Ok(()))
@debug.assert_eq(Validate::validate(AbsHeapType::no_func(), env), Ok(()))
@debug.assert_eq(Validate::validate(AbsHeapType::no_exn(), env), Ok(()))
}
// ============ HeapType Tests ============
///|
test "HeapType with AbsHeapType validates" {
let env = empty_env()
let ht = HeapType::abs(AbsHeapType::func())
@debug.assert_eq(Validate::validate(ht, env), Ok(()))
}
///|
test "HeapType with invalid TypeIdx fails" {
let env = empty_env()
let ht = HeapType::new(TypeIdx::new(0))
@debug.assert_eq(
Validate::validate(ht, env),
Err("No type exists for TypeIdx 0; global type count is 0"),
)
}
///|
test "HeapType with valid TypeIdx succeeds" {
let st = SubType::comp_type(CompType::func([], []))
let env = empty_env().with_types([st])
let ht = HeapType::new(TypeIdx::new(0))
@debug.assert_eq(Validate::validate(ht, env), Ok(()))
}
// ============ ValType Tests ============
///|
test "ValType NumType validates" {
let env = empty_env()
let vt = ValType::i32()
@debug.assert_eq(Validate::validate(vt, env), Ok(()))
}
///|
test "ValType VecType validates" {
let env = empty_env()
let t = ValType::v128()
@debug.assert_eq(Validate::validate(t, env), Ok(()))
}
///|
test "ValType BotType validates" {
let env = empty_env()
let t = ValType::bottom()
@debug.assert_eq(Validate::validate(t, env), Ok(()))
}
///|
test "ValType RefType with AbsHeapType validates" {
let env = empty_env()
let rt = RefType::abs(AbsHeapType::func())
let vt = ValType::ref_type(rt)
@debug.assert_eq(Validate::validate(vt, env), Ok(()))
}
// ============ RefType Tests ============
///|
test "RefType HeapTypeRefType with valid AbsHeapType validates" {
let env = empty_env()
let rt = RefType::new(true, HeapType::abs(AbsHeapType::any()))
@debug.assert_eq(Validate::validate(rt, env), Ok(()))
}
///|
test "RefType HeapTypeRefType nullable and non-nullable validate" {
let env = empty_env()
let rt_null = RefType::new(true, HeapType::abs(AbsHeapType::func()))
let rt_non_null = RefType::new(false, HeapType::abs(AbsHeapType::func()))
@debug.assert_eq(Validate::validate(rt_null, env), Ok(()))
@debug.assert_eq(Validate::validate(rt_non_null, env), Ok(()))
}
// ============ TypeIdx Tests ============
///|
test "TypeIdx validates when type exists" {
let st = SubType::comp_type(CompType::func([], []))
let env = empty_env().with_types([st])
let tidx = TypeIdx::new(0)
@debug.assert_eq(Validate::validate(tidx, env), Ok(()))
}
///|
test "TypeIdx fails when type does not exist" {
let env = empty_env()
let tidx = TypeIdx::new(0)
@debug.assert_eq(
Validate::validate(tidx, env),
Err("No type exists for TypeIdx 0; global type count is 0"),
)
}
///|
test "TypeIdx fails with out of bounds index" {
let st = SubType::comp_type(CompType::func([], []))
let env = empty_env().with_types([st])
let tidx = TypeIdx::new(5)
@debug.assert_eq(
Validate::validate(tidx, env),
Err("No type exists for TypeIdx 5; global type count is 1"),
)
}
// ============ LabelIdx Tests ============
///|
test "LabelIdx validates when label exists" {
let env = empty_env().with_labels([[]])
let lidx = LabelIdx::new(0)
@debug.assert_eq(Validate::validate(lidx, env), Ok(()))
}
///|
test "LabelIdx fails when label does not exist" {
let env = empty_env()
let lidx = LabelIdx::new(0)
@debug.assert_eq(Validate::validate(lidx, env), Err("Invalid LabelIdx"))
}
///|
test "LabelIdx fails with out of bounds index" {
let env = empty_env().with_labels([[]])
let lidx = LabelIdx::new(5)
@debug.assert_eq(Validate::validate(lidx, env), Err("Invalid LabelIdx"))
}
// ============ TagIdx Tests ============
///|
test "TagIdx validates when tag exists" {
let st = SubType::comp_type(CompType::func([], []))
let env = empty_env()
.with_types([st])
.with_tags([TagType::new(TypeIdx::new(0))])
let tidx = TagIdx::new(0)
@debug.assert_eq(Validate::validate(tidx, env), Ok(()))
}
///|
test "TagIdx fails when tag does not exist" {
let env = empty_env()
let tidx = TagIdx::new(0)
@debug.assert_eq(Validate::validate(tidx, env), Err("Invalid TagIdx"))
}
// ============ FuncIdx Tests ============
///|
test "FuncIdx validates when func exists" {
let env = empty_env().with_funcs([FuncType::new([], [])])
let fidx = FuncIdx::new(0)
@debug.assert_eq(Validate::validate(fidx, env), Ok(()))
}
///|
test "FuncIdx fails when func does not exist" {
let env = empty_env()
let fidx = FuncIdx::new(0)
@debug.assert_eq(Validate::validate(fidx, env), Err("invalid func index"))
}
// ============ TableIdx Tests ============
///|
test "TableIdx validates when table exists" {
let rt = RefType::abs(AbsHeapType::func())
let tt = TableType::new(rt, Limits::i32(0, None))
let env = empty_env().with_tables([tt])
let tidx = TableIdx::new(0)
@debug.assert_eq(Validate::validate(tidx, env), Ok(()))
}
///|
test "TableIdx fails when table does not exist" {
let env = empty_env()
let tidx = TableIdx::new(0)
@debug.assert_eq(Validate::validate(tidx, env), Err("invalid table index"))
}
// ============ MemIdx Tests ============
///|
test "MemIdx validates when memory exists" {
let mt = MemType::new(Limits::i32(0, None))
let env = empty_env().with_mems([mt])
let midx = MemIdx::new(0)
@debug.assert_eq(Validate::validate(midx, env), Ok(()))
}
///|
test "MemIdx fails when memory does not exist" {
let env = empty_env()
let midx = MemIdx::new(0)
@debug.assert_eq(Validate::validate(midx, env), Err("invalid mem index"))
}
// ============ GlobalIdx Tests ============
///|
test "GlobalIdx validates when global exists" {
let gt = GlobalType::new(ValType::i32(), false)
let env = empty_env().with_globals([gt])
let gidx = GlobalIdx::new(0)
@debug.assert_eq(Validate::validate(gidx, env), Ok(()))
}
///|
test "GlobalIdx fails when global does not exist" {
let env = empty_env()
let gidx = GlobalIdx::new(0)
@debug.assert_eq(Validate::validate(gidx, env), Err("invalid global index"))
}
// ============ Limits Tests (ValidateMax) ============
///|
test "I32Limits validates within max" {
let env = empty_env()
let lim = Limits::i32(10, Some(100))
@debug.assert_eq(ValidateMax::validate_max(lim, env, 1000), Ok(()))
}
///|
test "I32Limits validates with no upper bound" {
let env = empty_env()
let lim = Limits::i32(10, None)
@debug.assert_eq(ValidateMax::validate_max(lim, env, 1000), Ok(()))
}
///|
test "I32Limits fails when n exceeds k" {
let env = empty_env()
let lim = Limits::i32(2000, None)
@debug.assert_eq(
ValidateMax::validate_max(lim, env, 1000),
Err("Invalid range for limits"),
)
}
///|
test "I32Limits fails when m exceeds k" {
let env = empty_env()
let lim = Limits::i32(10, Some(2000))
@debug.assert_eq(
ValidateMax::validate_max(lim, env, 1000),
Err("Invalid range for limits"),
)
}
///|
test "I32Limits fails when n exceeds m" {
let env = empty_env()
let lim = Limits::i32(100, Some(10))
@debug.assert_eq(
ValidateMax::validate_max(lim, env, 1000),
Err("Invalid range for limits"),
)
}
///|
test "I64Limits validates within max" {
let env = empty_env()
let lim = Limits::i64(10, Some(100))
@debug.assert_eq(ValidateMax::validate_max(lim, env, 1000), Ok(()))
}
// ============ MemType Tests ============
///|
test "MemType validates with valid limits" {
let env = empty_env()
let mt = MemType::new(Limits::i32(0, Some(100)))
@debug.assert_eq(Validate::validate(mt, env), Ok(()))
}
///|
test "shared MemType validates with valid max" {
let env = empty_env()
let mt = MemType::new(Limits::i32(0, Some(100)), shared=true)
@debug.assert_eq(Validate::validate(mt, env), Ok(()))
}
///|
test "memory64 MemType validates with large max" {
let env = empty_env()
let mt = MemType::new(Limits::i64(5250UL, Some(7098475342658UL)))
@debug.assert_eq(Validate::validate(mt, env), Ok(()))
}
///|
test "shared MemType fails without max" {
let env = empty_env()
let mt = MemType::new(Limits::i32(0, None), shared=true)
@debug.assert_eq(
Validate::validate(mt, env),
Err("shared memories require a max"),
)
}
///|
test "MemType fails when limits exceed 65536" {
let env = empty_env()
let mt = MemType::new(Limits::i32(0, Some(100000)))
@debug.assert_eq(Validate::validate(mt, env), Err("Invalid range for limits"))
}
// ============ TableType Tests ============
///|
test "TableType validates with valid ref type and limits" {
let env = empty_env()
let rt = RefType::abs(AbsHeapType::func())
let tt = TableType::new(rt, Limits::i32(0, Some(100)))
@debug.assert_eq(Validate::validate(tt, env), Ok(()))
}
// ============ GlobalType Tests ============
///|
test "GlobalType validates with NumType" {
let env = empty_env()
let gt = GlobalType::new(ValType::i32(), false)
@debug.assert_eq(Validate::validate(gt, env), Ok(()))
}
///|
test "GlobalType validates mutable" {
let env = empty_env()
let gt = GlobalType::new(ValType::i64(), true)
@debug.assert_eq(Validate::validate(gt, env), Ok(()))
}
// ============ StorageType Tests ============
///|
test "StorageType ValType validates" {
let env = empty_env()
let st = StorageType::val_type(ValType::i32())
@debug.assert_eq(Validate::validate(st, env), Ok(()))
}
///|
test "StorageType PackType validates" {
let env = empty_env()
let st = StorageType::pack_type(PackType::i8())
@debug.assert_eq(Validate::validate(st, env), Ok(()))
}
// ============ FieldType Tests ============
///|
test "FieldType validates" {
let env = empty_env()
let ft = FieldType::new(StorageType::val_type(ValType::i32()), Const)
@debug.assert_eq(Validate::validate(ft, env), Ok(()))
}
// ============ FuncType Tests ============
///|
test "FuncType with empty params and results validates" {
let env = empty_env()
let ft = FuncType::new([], [])
@debug.assert_eq(Validate::validate(ft, env), Ok(()))
}
///|
test "FuncType with params and results validates" {
let env = empty_env()
let ft = FuncType::new([ValType::i32()], [ValType::i64()])
@debug.assert_eq(Validate::validate(ft, env), Ok(()))
}
// ============ CompType Tests ============
///|
test "CompType FuncCompType validates" {
let env = empty_env()
let ct = SubType::comp_type(CompType::func([], []))
@debug.assert_eq(Validate::validate(ct, env), Ok(()))
}
///|
test "CompType ArrayCompType validates" {
let env = empty_env()
let ft = FieldType::new(StorageType::val_type(ValType::i32()), Const)
let ct = SubType::comp_type(CompType::array(ft))
@debug.assert_eq(Validate::validate(ct, env), Ok(()))
}
///|
test "CompType StructCompType validates" {
let env = empty_env()
let ft = FieldType::new(StorageType::val_type(ValType::i32()), Const)
let ct = SubType::comp_type(CompType::struct_([ft]))
@debug.assert_eq(Validate::validate(ct, env), Ok(()))
}
///|
test "CompType StructCompType empty validates" {
let env = empty_env()
let ct = SubType::comp_type(CompType::struct_([]))
@debug.assert_eq(Validate::validate(ct, env), Ok(()))
}
// ============ SubType Tests ============
///|
test "SubType CompTypeSubType validates" {
let env = empty_env()
let st = SubType::comp_type(CompType::func([], []))
@debug.assert_eq(Validate::validate(st, env), Ok(()))
}
// ============ RecType Tests ============
///|
test "RecType SingleRecType validates" {
let env = empty_env()
let st = SubType::comp_type(CompType::func([], []))
let rt = RecType::new(st)
@debug.assert_eq(Validate::validate(rt, env), Ok(()))
}
///|
test "RecType GroupRecType validates" {
let env = empty_env()
let st1 = SubType::comp_type(CompType::func([], []))
let st2 = SubType::comp_type(
CompType::array(
FieldType::new(StorageType::val_type(ValType::i32()), Const),
),
)
let rt = RecType::group([st1, st2])
@debug.assert_eq(Validate::validate(rt, env), Ok(()))
}
// ============ Array[T] Validate Tests ============
///|
test "Array of ValType validates" {
let env = empty_env()
let arr : Array[ValType] = [ValType::i32(), ValType::i64()]
@debug.assert_eq(Validate::validate(arr, env), Ok(()))
}
///|
test "Empty array validates" {
let env = empty_env()
let arr : Array[ValType] = []
@debug.assert_eq(Validate::validate(arr, env), Ok(()))
}
// ============ BlockType Tests ============
///|
test "BlockType VoidBlockType validates" {
let env = empty_env()
@debug.assert_eq(Validate::validate(BlockType::void_(), env), Ok(()))
}
///|
test "BlockType ValTypeBlockType validates" {
let env = empty_env()
let bt = BlockType::val_type(ValType::i32())
@debug.assert_eq(Validate::validate(bt, env), Ok(()))
}
///|
test "BlockType TypeIdxBlockType with valid func type validates" {
let st = SubType::comp_type(CompType::func([], []))
let env = empty_env().with_types([st])
let bt = BlockType::type_idx(TypeIdx::new(0))
@debug.assert_eq(Validate::validate(bt, env), Ok(()))
}
///|
test "BlockType TypeIdxBlockType with invalid index fails" {
let env = empty_env()
let bt = BlockType::type_idx(TypeIdx::new(0))
@debug.assert_eq(
Validate::validate(bt, env),
Err("Invalid TypeIdx for block type"),
)
}
///|
test "BlockType TypeIdxBlockType with non-func type fails" {
let st = SubType::comp_type(
CompType::array(
FieldType::new(StorageType::val_type(ValType::i32()), Const),
),
)
let env = empty_env().with_types([st])
let bt = BlockType::type_idx(TypeIdx::new(0))
@debug.assert_eq(
Validate::validate(bt, env),
Err("Expected Func Type for block type"),
)
}
///|
test "validate_module accepts raw type-index loop with terminal return continuation" {
let mod = Module::new()
.with_type_sec(
TypeSec::new([
RecType::new(
SubType::comp_type(CompType::func([ValType::i32()], [ValType::i32()])),
),
RecType::new(SubType::comp_type(CompType::func([], [ValType::i64()]))),
]),
)
.with_func_sec(FuncSec::new([TypeIdx::new(1U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::i32_const(@lib.I32::new(0)),
Instruction::loop_(
BlockType::type_idx(TypeIdx::new(0U)),
Expr::new([Instruction::unreachable_()]),
),
Instruction::i64_const(@lib.I64::new(1L)),
Instruction::return_(),
Instruction::unreachable_(),
]),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(e) =>
fail(
"expected raw type-index loop terminal-return fixture to validate, got \{validation_error_message(e)}",
)
}
}
///|
test "validate_module accepts raw type-index loop branch-payload bypass with terminal return" {
let mod = Module::new()
.with_type_sec(
TypeSec::new([
RecType::new(SubType::comp_type(CompType::func([ValType::i32()], []))),
RecType::new(
SubType::comp_type(CompType::func([ValType::i32()], [ValType::i64()])),
),
RecType::new(
SubType::comp_type(CompType::func([ValType::i32()], [ValType::i32()])),
),
]),
)
.with_func_sec(FuncSec::new([TypeIdx::new(0U), TypeIdx::new(1U)]))
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::drop(),
]),
),
Func::new(
Locals::empty(),
Expr::new([
Instruction::i32_const(@lib.I32::new(0)),
Instruction::loop_(
BlockType::type_idx(TypeIdx::new(2U)),
Expr::new([
Instruction::block(
BlockType::val_type(ValType::i32()),
Expr::new([
Instruction::block(
BlockType::void_(),
Expr::new([
Instruction::i32_const(@lib.I32::new(1)),
Instruction::if_(
BlockType::val_type(ValType::i32()),
[
Instruction::i32_const(@lib.I32::new(0)),
Instruction::if_(
BlockType::val_type(ValType::i32()),
[Instruction::local_get(LocalIdx::new(0U))],
Some([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::call(FuncIdx::new(0U)),
Instruction::br(LabelIdx::new(2U)),
]),
),
],
Some([
Instruction::local_get(LocalIdx::new(0U)),
Instruction::call(FuncIdx::new(0U)),
Instruction::br(LabelIdx::new(1U)),
]),
),
Instruction::br(LabelIdx::new(1U)),
]),
),
Instruction::unreachable_(),
]),
),
Instruction::unreachable_(),
]),
),
Instruction::local_get(LocalIdx::new(0U)),
Instruction::call(FuncIdx::new(0U)),
Instruction::i64_const(@lib.I64::new(1L)),
Instruction::return_(),
Instruction::unreachable_(),
]),
),
]),
)
match validate_module(mod) {
Ok(_) => ()
Err(e) =>
fail(
"expected raw type-index loop branch-payload bypass fixture to validate, got \{validation_error_message(e)}",
)
}
}
// ============ ExternType Tests ============
///|
test "ExternType GlobalExternType validates" {
let env = empty_env()
let gt = GlobalType::new(ValType::i32(), false)
let et = ExternType::global(gt)
@debug.assert_eq(Validate::validate(et, env), Ok(()))
}
///|
test "ExternType MemExternType validates" {
let env = empty_env()
let mt = MemType::new(Limits::i32(0, Some(10)))
let et = ExternType::mem(mt)
@debug.assert_eq(Validate::validate(et, env), Ok(()))
}
///|
test "ExternType TableExternType validates" {
let env = empty_env()
let rt = RefType::abs(AbsHeapType::func())
let tt = TableType::new(rt, Limits::i32(0, Some(100)))
let et = ExternType::table(tt)
@debug.assert_eq(Validate::validate(et, env), Ok(()))
}
///|
test "ExternType FuncExternType validates when func type exists" {
let st = SubType::comp_type(CompType::func([], []))
let env = empty_env().with_types([st])
let et = ExternType::func(TypeIdx::new(0))
@debug.assert_eq(Validate::validate(et, env), Ok(()))
}
///|
test "ExternType FuncExternType fails when func type missing" {
let env = empty_env()
let et = ExternType::func(TypeIdx::new(0))
@debug.assert_eq(
Validate::validate(et, env),
Err("Invalid ExternType: Func extern type not found"),
)
}
// ============ ExternIdx Tests ============
///|
test "ExternIdx FuncExternIdx validates" {
let env = empty_env().with_funcs([FuncType::new([], [])])
let ei = ExternIdx::func(FuncIdx::new(0))
@debug.assert_eq(Validate::validate(ei, env), Ok(()))
}
///|
test "ExternIdx TableExternIdx validates" {
let rt = RefType::abs(AbsHeapType::func())
let tt = TableType::new(rt, Limits::i32(0, None))
let env = empty_env().with_tables([tt])
let ei = ExternIdx::table(TableIdx::new(0))
@debug.assert_eq(Validate::validate(ei, env), Ok(()))
}
///|
test "ExternIdx MemExternIdx validates" {
let mt = MemType::new(Limits::i32(0, None))
let env = empty_env().with_mems([mt])
let ei = ExternIdx::mem(MemIdx::new(0))
@debug.assert_eq(Validate::validate(ei, env), Ok(()))
}
///|
test "ExternIdx GlobalExternIdx validates" {
let gt = GlobalType::new(ValType::i32(), false)
let env = empty_env().with_globals([gt])
let ei = ExternIdx::global(GlobalIdx::new(0))
@debug.assert_eq(Validate::validate(ei, env), Ok(()))
}
// ============ Export Tests ============
///|
test "Export with valid FuncExternIdx validates" {
let env = empty_env().with_funcs([FuncType::new([], [])])
let exp = Export::new(Name::new("test"), ExternIdx::func(FuncIdx::new(0)))
@debug.assert_eq(Validate::validate(exp, env), Ok(()))
}
///|
test "Export with invalid index fails" {
let env = empty_env()
let exp = Export::new(Name::new("test"), ExternIdx::func(FuncIdx::new(0)))
@debug.assert_eq(Validate::validate(exp, env), Err("invalid func index"))
}
// ============ Import Tests ============
///|
test "Import with valid GlobalExternType validates" {
let env = empty_env()
let gt = GlobalType::new(ValType::i32(), false)
let imp = Import::new(
Name::new("mod"),
Name::new("name"),
ExternType::global(gt),
)
@debug.assert_eq(Validate::validate(imp, env), Ok(()))
}
// ============ DataMode Tests ============
///|
test "DataMode Passive validates" {
let env = empty_env()
@debug.assert_eq(Validate::validate(DataMode::passive(), env), Ok(()))
}
// ============ Data Tests ============
///|
test "Data with Passive mode validates" {
let env = empty_env()
let d = Data::new(DataMode::passive(), b"hello")
@debug.assert_eq(Validate::validate(d, env), Ok(()))
}
// ============ Catch Tests ============
///|
test "Catch CatchAll validates with valid label" {
let env = empty_env().with_labels([[]])
let c = Catch::all(LabelIdx::new(0))
@debug.assert_eq(Validate::validate(c, env), Ok(()))
}
///|
test "Catch CatchAllRef validates with valid label" {
let env = empty_env().with_labels([[]])
let c = Catch::all_ref(LabelIdx::new(0))
@debug.assert_eq(Validate::validate(c, env), Ok(()))
}
///|
test "Catch CatchAll fails with invalid label" {
let env = empty_env()
let c = Catch::all(LabelIdx::new(0))
@debug.assert_eq(Validate::validate(c, env), Err("Invalid LabelIdx"))
}
///|
fn validate_name_section_fixture_module() -> Module {
let struct_type = RecType::new(
SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::val_type(ValType::i32()), Const),
]),
),
)
let func_type = RecType::new(
SubType::comp_type(CompType::func([ValType::i32()], [ValType::i32()])),
)
let tag_type = RecType::new(SubType::comp_type(CompType::func([], [])))
let table_type = TableType::new(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
Limits::i32(1U, Some(1U)),
)
let name_sec = NameSec::new(
module_name=Some(Name::new("fixture")),
func_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("id"))])),
local_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([NameAssoc::new(0U, Name::new("x"))]),
),
]),
),
label_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([
NameAssoc::new(0U, Name::new("entry")),
NameAssoc::new(1U, Name::new("repeat")),
NameAssoc::new(2U, Name::new("branch")),
]),
),
]),
),
type_names=Some(
NameMap::new([
NameAssoc::new(0U, Name::new("Pair")),
NameAssoc::new(1U, Name::new("IdSig")),
NameAssoc::new(2U, Name::new("TagSig")),
]),
),
table_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("dispatch"))])),
memory_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("heap"))])),
global_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("flag"))])),
elem_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("targets"))])),
data_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("blob"))])),
field_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([NameAssoc::new(0U, Name::new("value"))]),
),
]),
),
tag_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("boom"))])),
)
Module::new()
.with_type_sec(TypeSec::new([struct_type, func_type, tag_type]))
.with_func_sec(FuncSec::new([TypeIdx::new(1U)]))
.with_table_sec(TableSec::new([Table::new(table_type, None)]))
.with_mem_sec(MemSec::new([MemType::new(Limits::i32(1U, Some(1U)))]))
.with_tag_sec(TagSec::new([TagType::new(TypeIdx::new(2U))]))
.with_global_sec(
GlobalSec::new([
Global::new(
GlobalType::new(ValType::i32(), false),
Expr::new([Instruction::i32_const(I32(1))]),
),
]),
)
.with_elem_sec(
ElemSec::new([
Elem::new(
ElemMode::active(
TableIdx::new(0U),
Expr::new([Instruction::i32_const(I32(0))]),
),
ElemKind::funcs([FuncIdx::new(0U)]),
),
]),
)
.with_data_sec(
DataSec::new([
Data::new(
DataMode::active(
MemIdx::new(0U),
Expr::new([Instruction::i32_const(I32(0))]),
),
b"fixture",
),
]),
)
.with_code_sec(
CodeSec::new([
Func::new(
Locals::empty(),
Expr::new([
Instruction::block(BlockType::void_(), Expr::new([])),
Instruction::loop_(BlockType::void_(), Expr::new([])),
Instruction::i32_const(I32(1)),
Instruction::if_(BlockType::void_(), [], Some([])),
Instruction::local_get(LocalIdx::new(0U)),
]),
),
]),
)
.with_name_sec(name_sec)
}
///|
fn validate_import_name_section_fixture_module() -> Module {
let func_type = RecType::new(
SubType::comp_type(CompType::func([ValType::i32()], [])),
)
Module::new()
.with_type_sec(TypeSec::new([func_type]))
.with_import_sec(
ImportSec::new([
Import::new(
Name::new("env"),
Name::new("f"),
ExternType::func(TypeIdx::new(0U)),
),
]),
)
}
///|
test "validate_module accepts structured name section" {
let mod = validate_name_section_fixture_module()
@debug.assert_eq(validate_module(mod), Ok(()))
}
///|
test "validate_module rejects out-of-range function name index in name section" {
let mod = validate_name_section_fixture_module().with_name_sec(
NameSec::new(
func_names=Some(NameMap::new([NameAssoc::new(1U, Name::new("bad"))])),
),
)
match validate_module(mod) {
Ok(_) => fail("expected invalid function name section validation failure")
Err(err) => {
assert_true(
validation_error_message(err).contains(
"function name index out of range",
),
)
@debug.assert_eq(
validation_error_diagnostic(err).issue,
ValidationIssue::NameSection(
"function name index out of range: func=1 total_funcs=1",
),
)
}
}
}
///|
test "validate_module rejects out-of-range type name index in name section" {
let mod = validate_name_section_fixture_module().with_name_sec(
NameSec::new(
type_names=Some(NameMap::new([NameAssoc::new(3U, Name::new("bad"))])),
),
)
match validate_module(mod) {
Ok(_) => fail("expected invalid type name section validation failure")
Err(err) => {
assert_true(
validation_error_message(err).contains("type name index out of range"),
)
@debug.assert_eq(
validation_error_diagnostic(err).issue,
ValidationIssue::NameSection(
"type name index out of range: type=3 total_types=3",
),
)
}
}
}
///|
test "validate_module rejects out-of-range local name index in name section" {
let mod = validate_name_section_fixture_module().with_name_sec(
NameSec::new(
module_name=Some(Name::new("fixture")),
func_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("id"))])),
local_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([NameAssoc::new(1U, Name::new("bad"))]),
),
]),
),
label_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([
NameAssoc::new(0U, Name::new("entry")),
NameAssoc::new(1U, Name::new("repeat")),
NameAssoc::new(2U, Name::new("branch")),
]),
),
]),
),
type_names=Some(
NameMap::new([
NameAssoc::new(0U, Name::new("Pair")),
NameAssoc::new(1U, Name::new("IdSig")),
NameAssoc::new(2U, Name::new("TagSig")),
]),
),
table_names=Some(
NameMap::new([NameAssoc::new(0U, Name::new("dispatch"))]),
),
memory_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("heap"))])),
global_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("flag"))])),
elem_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("targets"))])),
data_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("blob"))])),
field_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([NameAssoc::new(0U, Name::new("value"))]),
),
]),
),
tag_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("boom"))])),
),
)
match validate_module(mod) {
Ok(_) => fail("expected invalid local name section validation failure")
Err(err) => {
assert_true(
validation_error_message(err).contains("local name index out of range"),
)
@debug.assert_eq(
validation_error_diagnostic(err).issue,
ValidationIssue::NameSection(
"local name index out of range: func=0 local=1 total_locals=1",
),
)
}
}
}
///|
test "validate_module accepts imported function local names for params" {
let mod = validate_import_name_section_fixture_module().with_name_sec(
NameSec::new(
local_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([NameAssoc::new(0U, Name::new("x"))]),
),
]),
),
),
)
match validate_module(mod) {
Ok(_) => ()
Err(err) =>
fail(
"expected imported function local names to validate: \{validation_error_message(err)}",
)
}
}
///|
test "validate_module rejects out-of-range label name index in name section" {
let mod = validate_name_section_fixture_module().with_name_sec(
NameSec::new(
module_name=Some(Name::new("fixture")),
func_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("id"))])),
local_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([NameAssoc::new(0U, Name::new("x"))]),
),
]),
),
label_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([NameAssoc::new(3U, Name::new("too-far"))]),
),
]),
),
type_names=Some(
NameMap::new([
NameAssoc::new(0U, Name::new("Pair")),
NameAssoc::new(1U, Name::new("IdSig")),
NameAssoc::new(2U, Name::new("TagSig")),
]),
),
table_names=Some(
NameMap::new([NameAssoc::new(0U, Name::new("dispatch"))]),
),
memory_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("heap"))])),
global_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("flag"))])),
elem_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("targets"))])),
data_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("blob"))])),
field_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([NameAssoc::new(0U, Name::new("value"))]),
),
]),
),
tag_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("boom"))])),
),
)
match validate_module(mod) {
Ok(_) => fail("expected invalid label name section validation failure")
Err(err) => {
assert_true(
validation_error_message(err).contains("label name index out of range"),
)
@debug.assert_eq(
validation_error_diagnostic(err).issue,
ValidationIssue::NameSection(
"label name index out of range: func=0 label=3 total_labels=3",
),
)
}
}
}
///|
test "validate_module rejects imported function label names because imports have no body labels" {
let mod = validate_import_name_section_fixture_module().with_name_sec(
NameSec::new(
label_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([NameAssoc::new(0U, Name::new("entry"))]),
),
]),
),
),
)
match validate_module(mod) {
Ok(_) =>
fail("expected invalid imported-function label name section failure")
Err(err) => {
assert_true(
validation_error_message(err).contains("label name index out of range"),
)
@debug.assert_eq(
validation_error_diagnostic(err).issue,
ValidationIssue::NameSection(
"label name index out of range: func=0 label=0 total_labels=0",
),
)
}
}
}
///|
test "validate_module rejects out-of-range data name index in name section" {
let mod = validate_name_section_fixture_module().with_name_sec(
NameSec::new(
module_name=Some(Name::new("fixture")),
func_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("id"))])),
local_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([NameAssoc::new(0U, Name::new("x"))]),
),
]),
),
label_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([
NameAssoc::new(0U, Name::new("entry")),
NameAssoc::new(1U, Name::new("repeat")),
NameAssoc::new(2U, Name::new("branch")),
]),
),
]),
),
type_names=Some(
NameMap::new([
NameAssoc::new(0U, Name::new("Pair")),
NameAssoc::new(1U, Name::new("IdSig")),
NameAssoc::new(2U, Name::new("TagSig")),
]),
),
table_names=Some(
NameMap::new([NameAssoc::new(0U, Name::new("dispatch"))]),
),
memory_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("heap"))])),
global_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("flag"))])),
elem_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("targets"))])),
data_names=Some(NameMap::new([NameAssoc::new(1U, Name::new("bad"))])),
field_names=Some(
IndirectNameMap::new([
IndirectNameAssoc::new(
0U,
NameMap::new([NameAssoc::new(0U, Name::new("value"))]),
),
]),
),
tag_names=Some(NameMap::new([NameAssoc::new(0U, Name::new("boom"))])),
),
)
match validate_module(mod) {
Ok(_) => fail("expected invalid data name section validation failure")
Err(err) => {
assert_true(
validation_error_message(err).contains("data name index out of range"),
)
@debug.assert_eq(
validation_error_diagnostic(err).issue,
ValidationIssue::NameSection(
"data name index out of range: data=1 total_data=1",
),
)
}
}
}