@@ -7314,6 +7314,26 @@ pub(super) fn lower_logical_reduction_intrinsic_ast( |
| 7314 | 7314 | return None; |
| 7315 | 7315 | } |
| 7316 | 7316 | |
| 7317 | + // F2018 §16.9.46: COUNT(MASK, DIM=k) returns an integer ARRAY of |
| 7318 | + // rank N-1, not a scalar. The scalar runtime helper below would |
| 7319 | + // collapse the entire mask into one i32 — when assigned into a |
| 7320 | + // rank-1 destination (e.g. `n = count(mask, 1)` in stdlib_stats |
| 7321 | + // var_mask_2), the integer count would then be passed as the |
| 7322 | + // source descriptor pointer to afs_assign_allocatable, dereferencing |
| 7323 | + // a tiny address (e.g. 0x3 for count=3) and aborting. Bail only |
| 7324 | + // when an actual dim= argument is present; `count(mask, kind=int64)` |
| 7325 | + // is still a scalar reduction (kind is just the result kind) and |
| 7326 | + // must keep using the scalar path. |
| 7327 | + if name == "count" { |
| 7328 | + let has_dim = args.iter().enumerate().any(|(i, a)| { |
| 7329 | + let kw = a.keyword.as_deref().map(|s| s.to_lowercase()); |
| 7330 | + matches!(kw.as_deref(), Some("dim")) || (i == 1 && kw.is_none()) |
| 7331 | + }); |
| 7332 | + if has_dim { |
| 7333 | + return None; |
| 7334 | + } |
| 7335 | + } |
| 7336 | + |
| 7317 | 7337 | let arg0 = args.first().and_then(|arg| { |
| 7318 | 7338 | if let SectionSubscript::Element(expr) = &arg.value { |
| 7319 | 7339 | Some(expr) |
@@ -23455,6 +23475,81 @@ pub(super) fn lower_transfer_array_expr_descriptor( |
| 23455 | 23475 | /// rank-(N-1) descriptor. Returns None when DIM is absent (scalar |
| 23456 | 23476 | /// SUM(ARRAY) lives in the standard intrinsic path) or when the |
| 23457 | 23477 | /// optional MASK is supplied (no mask runtime support yet). |
| 23478 | +/// F2018 §16.9.46 COUNT(MASK, DIM=k) returns an integer array of rank |
| 23479 | +/// N-1 with per-slice true-element counts along dimension k. The |
| 23480 | +/// scalar logical-reduction path lowers `count` to `afs_array_count_logical`, |
| 23481 | +/// which returns a single i32 — with DIM= present that's incorrect: the |
| 23482 | +/// stdlib_stats `var_mask_2_*` body assigns the result into a real |
| 23483 | +/// rank-1 array `n`, and the compiler then passed the scalar count as |
| 23484 | +/// the source descriptor pointer to afs_assign_allocatable, crashing |
| 23485 | +/// with a misaligned-pointer dereference at address 0x3 (the count |
| 23486 | +/// value masquerading as a pointer). |
| 23487 | +pub(super) fn lower_array_count_dim_descriptor( |
| 23488 | + b: &mut FuncBuilder, |
| 23489 | + locals: &HashMap<String, LocalInfo>, |
| 23490 | + args: &[crate::ast::expr::Argument], |
| 23491 | + st: &SymbolTable, |
| 23492 | + type_layouts: Option<&crate::sema::type_layout::TypeLayoutRegistry>, |
| 23493 | + internal_funcs: Option<&HashMap<String, u32>>, |
| 23494 | + contained_host_refs: Option<&HashMap<String, Vec<String>>>, |
| 23495 | + descriptor_params: Option<&HashMap<String, Vec<bool>>>, |
| 23496 | +) -> Option<(ValueId, IrType)> { |
| 23497 | + use crate::ast::expr::SectionSubscript; |
| 23498 | + if args.is_empty() { |
| 23499 | + return None; |
| 23500 | + } |
| 23501 | + let SectionSubscript::Element(mask_expr) = &args[0].value else { |
| 23502 | + return None; |
| 23503 | + }; |
| 23504 | + let mut dim_expr: Option<&crate::ast::expr::SpannedExpr> = None; |
| 23505 | + for (i, arg) in args.iter().enumerate() { |
| 23506 | + let kw = arg.keyword.as_deref().map(|s| s.to_lowercase()); |
| 23507 | + match (i, kw.as_deref()) { |
| 23508 | + (1, None) | (_, Some("dim")) => { |
| 23509 | + if let SectionSubscript::Element(e) = &arg.value { |
| 23510 | + dim_expr = Some(e); |
| 23511 | + } |
| 23512 | + } |
| 23513 | + _ => {} |
| 23514 | + } |
| 23515 | + } |
| 23516 | + let dim_expr = dim_expr?; |
| 23517 | + |
| 23518 | + let (mask_desc, _) = lower_array_expr_descriptor( |
| 23519 | + b, |
| 23520 | + locals, |
| 23521 | + mask_expr, |
| 23522 | + st, |
| 23523 | + type_layouts, |
| 23524 | + internal_funcs, |
| 23525 | + contained_host_refs, |
| 23526 | + descriptor_params, |
| 23527 | + )?; |
| 23528 | + |
| 23529 | + let dim_raw = super::expr::lower_expr_with_optional_layouts(b, locals, dim_expr, st, type_layouts); |
| 23530 | + let dim_val = match b.func().value_type(dim_raw) { |
| 23531 | + Some(IrType::Int(IntWidth::I32)) => dim_raw, |
| 23532 | + Some(IrType::Int(_)) => b.int_trunc(dim_raw, IntWidth::I32), |
| 23533 | + _ => dim_raw, |
| 23534 | + }; |
| 23535 | + |
| 23536 | + let result_desc = b.alloca(IrType::Array(Box::new(IrType::Int(IntWidth::I8)), 384)); |
| 23537 | + let zero_i32 = b.const_i32(0); |
| 23538 | + let sz384 = b.const_i64(384); |
| 23539 | + b.call( |
| 23540 | + FuncRef::External("memset".into()), |
| 23541 | + vec![result_desc, zero_i32, sz384], |
| 23542 | + IrType::Ptr(Box::new(IrType::Int(IntWidth::I8))), |
| 23543 | + ); |
| 23544 | + |
| 23545 | + b.call( |
| 23546 | + FuncRef::External("afs_array_count_logical_dim".into()), |
| 23547 | + vec![mask_desc, dim_val, result_desc], |
| 23548 | + IrType::Void, |
| 23549 | + ); |
| 23550 | + Some((result_desc, IrType::Int(IntWidth::I32))) |
| 23551 | +} |
| 23552 | + |
| 23458 | 23553 | pub(super) fn lower_array_sum_dim_descriptor( |
| 23459 | 23554 | b: &mut FuncBuilder, |
| 23460 | 23555 | locals: &HashMap<String, LocalInfo>, |
@@ -25504,6 +25599,32 @@ pub(super) fn lower_array_expr_descriptor( |
| 25504 | 25599 | return Some(result); |
| 25505 | 25600 | } |
| 25506 | 25601 | } |
| 25602 | + // F2018 §16.9.46: COUNT(MASK, DIM=k) returns an integer |
| 25603 | + // array of rank N-1. Without this dim-aware path the |
| 25604 | + // scalar logical-reduction lowering returns a single i32 |
| 25605 | + // and the caller copies that into the destination array |
| 25606 | + // descriptor's source slot — afs_assign_allocatable then |
| 25607 | + // dereferences the count value as a pointer and aborts. |
| 25608 | + if name.eq_ignore_ascii_case("count") { |
| 25609 | + let has_dim = args.iter().enumerate().any(|(i, a)| { |
| 25610 | + let kw = a.keyword.as_deref().map(|s| s.to_lowercase()); |
| 25611 | + matches!(kw.as_deref(), Some("dim")) || (i == 1 && kw.is_none()) |
| 25612 | + }); |
| 25613 | + if has_dim { |
| 25614 | + if let Some(result) = lower_array_count_dim_descriptor( |
| 25615 | + b, |
| 25616 | + locals, |
| 25617 | + args, |
| 25618 | + st, |
| 25619 | + type_layouts, |
| 25620 | + internal_funcs, |
| 25621 | + contained_host_refs, |
| 25622 | + descriptor_params, |
| 25623 | + ) { |
| 25624 | + return Some(result); |
| 25625 | + } |
| 25626 | + } |
| 25627 | + } |
| 25507 | 25628 | // F2018 §16.9.135: MERGE is elemental. When at least one of the |
| 25508 | 25629 | // first two actuals (or the mask) is an array, the result is |
| 25509 | 25630 | // an array; we materialize it via per-element select, leaving |