fix(joinir): stabilize phase1883 latch/entry preds
This commit is contained in:
@ -1,6 +1,10 @@
|
||||
# Self Current Task — Now (main)
|
||||
|
||||
## Current Focus: Phase 29ad COMPLETE(Naming SSOT)
|
||||
## Current Focus: Phase 29ae (JoinIR Regression Pack)
|
||||
|
||||
**2025-12-28: Phase 29ae P0 完了** ✅
|
||||
- 目的: JoinIR の最小回帰セットを SSOT で固定
|
||||
- 入口: `docs/development/current/main/phases/phase-29ae/README.md`
|
||||
|
||||
**2025-12-28: Phase 29ad 完了** ✅
|
||||
- 目的: Pattern6/7 fixture/smoke の命名規約を SSOT 化し、variant 名を明示して迷いを消す
|
||||
|
||||
@ -8,6 +8,9 @@ Related:
|
||||
|
||||
## 直近(JoinIR/selfhost)
|
||||
|
||||
- **Phase 29ae(✅ COMPLETE): JoinIR Regression Pack (docs-first)**
|
||||
- 入口: `docs/development/current/main/phases/phase-29ae/README.md`
|
||||
|
||||
- **Phase 29ad(✅ COMPLETE): Naming SSOT for Pattern6/7 fixtures**
|
||||
- 入口: `docs/development/current/main/phases/phase-29ad/README.md`
|
||||
|
||||
|
||||
21
docs/development/current/main/phases/phase-29ae/README.md
Normal file
21
docs/development/current/main/phases/phase-29ae/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Phase 29ae: JoinIR Regression Pack (docs-first)
|
||||
|
||||
Goal: JoinIR の最小回帰セットを SSOT として固定する。
|
||||
|
||||
## Regression pack (SSOT)
|
||||
|
||||
- Pattern2: `phase29ab_pattern2_*`
|
||||
- Pattern6: `phase29ab_pattern6_*`
|
||||
- Pattern7: `phase29ab_pattern7_*`
|
||||
- Merge/Phi代表: `apps/tests/phase1883_nested_minimal.hako`(RC=9)
|
||||
|
||||
## Commands
|
||||
|
||||
- `./tools/smokes/v2/run.sh --profile integration --filter "phase29ab_pattern2_"`
|
||||
- `./tools/smokes/v2/run.sh --profile integration --filter "phase29ab_pattern6_"`
|
||||
- `./tools/smokes/v2/run.sh --profile integration --filter "phase29ab_pattern7_"`
|
||||
- `./tools/smokes/v2/run.sh --profile integration --filter "phase1883_"`
|
||||
|
||||
## Status
|
||||
|
||||
- phase1883: PASS(RC=9 を成功扱い)
|
||||
@ -424,11 +424,22 @@ impl LoopHeaderPhiBuilder {
|
||||
})?
|
||||
};
|
||||
|
||||
// Step 3: Compute entry predecessors (header_preds - latch_block)
|
||||
// Step 3: Compute entry predecessors (entry_incoming blocks + host entry).
|
||||
// Phase 257 P1.2-FIX: Multiple entry preds are OK (bb0 host + bb10 JoinIR main)
|
||||
let mut entry_pred_set: std::collections::BTreeSet<BasicBlockId> =
|
||||
std::collections::BTreeSet::new();
|
||||
for entry in info.carrier_phis.values() {
|
||||
entry_pred_set.insert(entry.entry_incoming.0);
|
||||
}
|
||||
if let Some(host_entry_block) = host_entry_block_opt {
|
||||
entry_pred_set.insert(host_entry_block);
|
||||
}
|
||||
// Latch block is never an entry predecessor.
|
||||
entry_pred_set.remove(&latch_block);
|
||||
|
||||
let mut entry_preds: Vec<BasicBlockId> = header_preds
|
||||
.iter()
|
||||
.filter(|&&pred| pred != latch_block)
|
||||
.filter(|&&pred| entry_pred_set.contains(&pred))
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
@ -450,6 +461,13 @@ impl LoopHeaderPhiBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Latch preds are all header predecessors that are not entry preds.
|
||||
let latch_preds: Vec<BasicBlockId> = header_preds
|
||||
.iter()
|
||||
.filter(|&&pred| !entry_pred_set.contains(&pred))
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
// Step 4: Validate at least one entry predecessor
|
||||
if entry_preds.is_empty() {
|
||||
return Err(format!(
|
||||
@ -463,8 +481,8 @@ impl LoopHeaderPhiBuilder {
|
||||
let host_desc = host_entry_block_opt.map_or_else(|| "None".to_string(), |bb| format!("bb{}", bb.0));
|
||||
trace.stderr_if(
|
||||
&format!(
|
||||
"[joinir/header-phi] Entry predecessors: {:?} (latch=bb{}, host={}, total_preds={})",
|
||||
entry_preds, latch_block.0, host_desc, header_preds.len()
|
||||
"[joinir/header-phi] Entry predecessors: {:?} (latch=bb{}, host={}, total_preds={}, latch_preds={:?})",
|
||||
entry_preds, latch_block.0, host_desc, header_preds.len(), latch_preds
|
||||
),
|
||||
true,
|
||||
);
|
||||
@ -499,14 +517,16 @@ impl LoopHeaderPhiBuilder {
|
||||
// Phase 257 P1.2-FIX: Handle multiple entry predecessors (bb0 host + bb10 JoinIR main)
|
||||
for (name, entry) in &info.carrier_phis {
|
||||
let (_stored_entry_block, entry_val) = entry.entry_incoming; // Use value only
|
||||
let (latch_block_stored, latch_val) = entry.latch_incoming.unwrap();
|
||||
let (_latch_block_stored, latch_val) = entry.latch_incoming.unwrap();
|
||||
|
||||
// Build PHI inputs: all entry preds use same init value, latch uses next value
|
||||
// Build PHI inputs: entry preds use init value, latch preds use next value
|
||||
let mut phi_inputs = Vec::new();
|
||||
for &entry_pred in &entry_preds {
|
||||
phi_inputs.push((entry_pred, entry_val));
|
||||
}
|
||||
phi_inputs.push((latch_block_stored, latch_val));
|
||||
for &latch_pred in &latch_preds {
|
||||
phi_inputs.push((latch_pred, latch_val));
|
||||
}
|
||||
|
||||
let phi = MirInstruction::Phi {
|
||||
dst: entry.phi_dst,
|
||||
|
||||
@ -10,6 +10,8 @@ use crate::mir::builder::control_flow::joinir::merge::loop_header_phi_info::Loop
|
||||
use crate::mir::builder::control_flow::joinir::merge::tail_call_classifier::TailCallKind;
|
||||
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
|
||||
use crate::mir::{BasicBlockId, ValueId};
|
||||
use crate::config::env::joinir_dev_enabled;
|
||||
use crate::mir::builder::control_flow::joinir::trace;
|
||||
|
||||
pub(in crate::mir::builder::control_flow::joinir::merge) fn record_if_backedge(
|
||||
tail_call_kind: TailCallKind,
|
||||
@ -24,7 +26,35 @@ pub(in crate::mir::builder::control_flow::joinir::merge) fn record_if_backedge(
|
||||
|
||||
let Some(boundary) = boundary else { return };
|
||||
|
||||
if loop_header_phi_info
|
||||
.carrier_phis
|
||||
.values()
|
||||
.any(|entry| entry.latch_incoming.is_some())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(loop_var_name) = &boundary.loop_var_name {
|
||||
debug_assert!(
|
||||
!args.is_empty(),
|
||||
"Phase 29ae Fail-Fast: BackEdge latch args empty for loop var '{}'",
|
||||
loop_var_name
|
||||
);
|
||||
if joinir_dev_enabled() {
|
||||
if let Some(entry) = loop_header_phi_info.carrier_phis.get(loop_var_name) {
|
||||
if let Some(&arg0) = args.first() {
|
||||
if arg0 == entry.entry_incoming.1 {
|
||||
trace::trace().stderr_if(
|
||||
&format!(
|
||||
"[joinir/latch] warn: loop_var '{}' latch arg matches entry_incoming {:?}",
|
||||
loop_var_name, arg0
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(&latch_value) = args.first() {
|
||||
loop_header_phi_info.set_latch_incoming(loop_var_name, new_block_id, latch_value);
|
||||
}
|
||||
@ -52,4 +82,3 @@ pub(in crate::mir::builder::control_flow::joinir::merge) fn record_if_backedge(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -54,7 +54,8 @@ pub(super) fn resolve_target_func_name<'a>(
|
||||
/// Used to classify LoopEntry for tail-call handling.
|
||||
pub(super) fn is_joinir_loop_entry_source(
|
||||
func_name: &str,
|
||||
entry_func_name: Option<&str>,
|
||||
old_block_id: BasicBlockId,
|
||||
func_entry_block: BasicBlockId,
|
||||
) -> bool {
|
||||
entry_func_name.map(|name| name != func_name).unwrap_or(false)
|
||||
func_name == canonical_names::MAIN && old_block_id == func_entry_block
|
||||
}
|
||||
|
||||
@ -55,10 +55,13 @@ pub(super) fn process_block_instructions(
|
||||
// First pass: Filter instructions
|
||||
for inst in &old_block.instructions {
|
||||
// Skip Copy instructions that overwrite PHI dsts
|
||||
if is_loop_header_with_phi {
|
||||
if let MirInstruction::Copy { dst, src: _ } = inst {
|
||||
if is_loop_header_with_phi && is_loop_header_entry_block {
|
||||
if let MirInstruction::Copy { dst, src } = inst {
|
||||
let dst_remapped = remapper.get_value(*dst).unwrap_or(*dst);
|
||||
if InstructionFilterBox::should_skip_copy_overwriting_phi(dst_remapped, phi_dst_ids_for_block) {
|
||||
let is_boundary_input = boundary_input_set.contains(src);
|
||||
if is_boundary_input
|
||||
&& InstructionFilterBox::should_skip_copy_overwriting_phi(dst_remapped, phi_dst_ids_for_block)
|
||||
{
|
||||
log!(
|
||||
verbose,
|
||||
"[plan_rewrites] Skipping loop header Copy to PHI dst {:?}",
|
||||
@ -112,20 +115,23 @@ pub(super) fn process_block_instructions(
|
||||
}
|
||||
|
||||
// Skip Copy instructions that overwrite header PHI dsts
|
||||
if let MirInstruction::Copy { dst, src: _ } = inst {
|
||||
let remapped_dst = remapper.get_value(*dst).unwrap_or(*dst);
|
||||
let is_header_phi_dst = loop_header_phi_info
|
||||
.carrier_phis
|
||||
.values()
|
||||
.any(|entry| entry.phi_dst == remapped_dst);
|
||||
if is_loop_header_entry_block {
|
||||
if let MirInstruction::Copy { dst, src } = inst {
|
||||
let remapped_dst = remapper.get_value(*dst).unwrap_or(*dst);
|
||||
let is_header_phi_dst = loop_header_phi_info
|
||||
.carrier_phis
|
||||
.values()
|
||||
.any(|entry| entry.phi_dst == remapped_dst);
|
||||
let is_boundary_input = boundary_input_set.contains(src);
|
||||
|
||||
if is_header_phi_dst {
|
||||
log!(
|
||||
verbose,
|
||||
"[plan_rewrites] Skipping Copy that overwrites header PHI dst {:?}",
|
||||
remapped_dst
|
||||
);
|
||||
continue;
|
||||
if is_header_phi_dst && is_boundary_input {
|
||||
log!(
|
||||
verbose,
|
||||
"[plan_rewrites] Skipping Copy that overwrites header PHI dst {:?}",
|
||||
remapped_dst
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -153,13 +153,12 @@ pub(in crate::mir::builder::control_flow::joinir::merge) fn plan_rewrites(
|
||||
let mut blocks_merge: Vec<_> = func.blocks.iter().collect();
|
||||
blocks_merge.sort_by_key(|(id, _)| id.0);
|
||||
|
||||
// Determine if this is the loop header entry block (loop_step entry).
|
||||
let is_loop_header_entry_block = entry_func_name == Some(func_name.as_str())
|
||||
&& blocks_merge.first().map(|(id, _)| **id) == Some(func.entry_block);
|
||||
// Determine if this function is the loop header (loop_step).
|
||||
let is_loop_header_func = entry_func_name == Some(func_name.as_str());
|
||||
|
||||
// Check if loop header has PHIs
|
||||
let is_loop_header_with_phi =
|
||||
is_loop_header_entry_block && !loop_header_phi_info.carrier_phis.is_empty();
|
||||
is_loop_header_func && !loop_header_phi_info.carrier_phis.is_empty();
|
||||
|
||||
// Collect PHI dst IDs for this block (if loop header)
|
||||
let phi_dst_ids_for_block: HashSet<ValueId> =
|
||||
@ -191,6 +190,9 @@ pub(in crate::mir::builder::control_flow::joinir::merge) fn plan_rewrites(
|
||||
let mut new_block = BasicBlock::new(new_block_id);
|
||||
|
||||
// PHASE 2: Instruction rewriting (extracted)
|
||||
let is_loop_header_entry_block =
|
||||
is_loop_header_func && *old_block_id == func.entry_block;
|
||||
|
||||
let (filtered_insts, tail_target) = instruction_rewrite::process_block_instructions(
|
||||
old_block,
|
||||
remapper,
|
||||
|
||||
@ -8,17 +8,13 @@
|
||||
//! - Record latch incoming for loop header PHI
|
||||
|
||||
// Import helpers from entry_resolver
|
||||
use super::entry_resolver::{resolve_target_func_name, is_joinir_loop_entry_source};
|
||||
use super::entry_resolver::resolve_target_func_name;
|
||||
|
||||
// Rewriter siblings (2 super:: up from plan/ to stages/, then 1 more to rewriter/)
|
||||
use super::super::super::{
|
||||
rewrite_context::RewriteContext,
|
||||
latch_incoming_recorder,
|
||||
};
|
||||
use super::super::super::rewrite_context::RewriteContext;
|
||||
|
||||
// Merge level (3 super:: up from plan/ to stages/, then 1 more to rewriter/, then 1 more to merge/)
|
||||
use super::super::super::super::{
|
||||
tail_call_classifier::classify_tail_call,
|
||||
loop_header_phi_info::LoopHeaderPhiInfo,
|
||||
trace,
|
||||
};
|
||||
@ -50,11 +46,11 @@ pub(super) fn process_tail_call_params(
|
||||
continuation_candidates: &BTreeSet<String>,
|
||||
is_loop_header_entry_block: bool,
|
||||
is_loop_header_with_phi: bool,
|
||||
boundary: Option<&JoinInlineBoundary>,
|
||||
_boundary: Option<&JoinInlineBoundary>,
|
||||
loop_header_phi_info: &mut LoopHeaderPhiInfo,
|
||||
remapper: &mut JoinIrIdRemapper,
|
||||
ctx: &RewriteContext,
|
||||
new_block_id: BasicBlockId,
|
||||
_new_block_id: BasicBlockId,
|
||||
verbose: bool,
|
||||
) -> Result<(), String> {
|
||||
let trace_obj = trace::trace();
|
||||
@ -80,20 +76,6 @@ pub(super) fn process_tail_call_params(
|
||||
.map(|name| entry_func_name == Some(name))
|
||||
.unwrap_or(false);
|
||||
|
||||
// Phase 287 P2: Calculate tail_call_kind early for latch incoming logic
|
||||
// Only treat MAIN's entry block as entry-like (not loop_step's entry block)
|
||||
let is_entry_like_block_for_latch =
|
||||
is_joinir_loop_entry_source(func_name, entry_func_name);
|
||||
|
||||
// CRITICAL: Argument order must match merge level classify_tail_call()
|
||||
let tail_call_kind = classify_tail_call(
|
||||
is_entry_like_block_for_latch,
|
||||
!loop_header_phi_info.carrier_phis.is_empty(),
|
||||
boundary.is_some(),
|
||||
is_target_continuation,
|
||||
is_target_loop_entry,
|
||||
);
|
||||
|
||||
if let Some(target_func_name) = target_func_name {
|
||||
if let Some(target_params) = function_params.get(target_func_name) {
|
||||
|
||||
@ -114,7 +96,10 @@ pub(super) fn process_tail_call_params(
|
||||
verbose,
|
||||
"[plan_rewrites] Skip param bindings in header block (PHIs define carriers)"
|
||||
);
|
||||
} else if (is_recursive_call || is_target_loop_entry) && is_loop_header_with_phi {
|
||||
} else if (is_recursive_call || is_target_loop_entry)
|
||||
&& is_loop_header_with_phi
|
||||
&& is_loop_header_entry_block
|
||||
{
|
||||
// Update remapper mappings for continuation instructions
|
||||
for (i, arg_val_remapped) in args.iter().enumerate() {
|
||||
if i < target_params.len() {
|
||||
@ -157,7 +142,7 @@ pub(super) fn process_tail_call_params(
|
||||
.values()
|
||||
.any(|entry| entry.phi_dst == param_val_dst);
|
||||
|
||||
if is_header_phi_dst {
|
||||
if is_loop_header_entry_block && is_header_phi_dst {
|
||||
log!(
|
||||
verbose,
|
||||
"[plan_rewrites] Skip param binding to PHI dst {:?}",
|
||||
@ -176,16 +161,5 @@ pub(super) fn process_tail_call_params(
|
||||
}
|
||||
}
|
||||
|
||||
// Record latch incoming for loop header PHI (SSOT)
|
||||
// Phase 287 P2: BackEdge のみ latch 記録(LoopEntry main → loop_step を除外)
|
||||
// CRITICAL: Do not move this call - exact location matters
|
||||
latch_incoming_recorder::record_if_backedge(
|
||||
tail_call_kind,
|
||||
boundary,
|
||||
new_block_id,
|
||||
args,
|
||||
loop_header_phi_info,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ use super::super::super::{
|
||||
plan_box::RewrittenBlocks,
|
||||
return_converter_box::ReturnConverterBox,
|
||||
carrier_inputs_collector::CarrierInputsCollector,
|
||||
latch_incoming_recorder,
|
||||
terminator::{remap_branch, remap_jump},
|
||||
};
|
||||
|
||||
@ -51,8 +52,8 @@ pub(super) fn process_block_terminator(
|
||||
found_tail_call: bool,
|
||||
tail_call_target: Option<(BasicBlockId, &[ValueId])>,
|
||||
func_name: &str,
|
||||
_func: &MirFunction,
|
||||
_old_block_id: BasicBlockId,
|
||||
func: &MirFunction,
|
||||
old_block_id: BasicBlockId,
|
||||
new_block_id: BasicBlockId,
|
||||
remapper: &JoinIrIdRemapper,
|
||||
local_block_map: &BTreeMap<BasicBlockId, BasicBlockId>,
|
||||
@ -62,7 +63,7 @@ pub(super) fn process_block_terminator(
|
||||
is_continuation_candidate: bool,
|
||||
is_skippable_continuation: bool,
|
||||
boundary: Option<&JoinInlineBoundary>,
|
||||
loop_header_phi_info: &LoopHeaderPhiInfo,
|
||||
loop_header_phi_info: &mut LoopHeaderPhiInfo,
|
||||
ctx: &RewriteContext,
|
||||
result: &mut RewrittenBlocks,
|
||||
verbose: bool,
|
||||
@ -243,7 +244,7 @@ pub(super) fn process_block_terminator(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some((target_block, _args)) = tail_call_target {
|
||||
} else if let Some((target_block, args)) = tail_call_target {
|
||||
// Tail call: Set Jump terminator
|
||||
// Classify tail call and determine actual target
|
||||
let target_func_name = resolve_target_func_name(&ctx.function_entry_map, target_block);
|
||||
@ -259,7 +260,7 @@ pub(super) fn process_block_terminator(
|
||||
|
||||
// Phase 287 P2: host entry block からの呼び出しを LoopEntry 扱いにする
|
||||
// (loop header func の entry block は含めない)
|
||||
let is_entry_like_block = is_joinir_loop_entry_source(func_name, entry_func_name);
|
||||
let is_entry_like_block = is_joinir_loop_entry_source(func_name, old_block_id, func.entry_block);
|
||||
|
||||
// CRITICAL: Argument order must match merge level classify_tail_call()
|
||||
let tail_call_kind = classify_tail_call(
|
||||
@ -270,6 +271,65 @@ pub(super) fn process_block_terminator(
|
||||
is_target_loop_entry,
|
||||
);
|
||||
|
||||
// SSOT: record latch incoming only when BackEdge is confirmed
|
||||
if tail_call_kind == TailCallKind::BackEdge {
|
||||
let mut latch_args: Vec<ValueId> = Vec::new();
|
||||
let mut loop_var_updated = false;
|
||||
|
||||
if let Some(boundary) = boundary {
|
||||
for (idx, carrier_name) in loop_header_phi_info.carrier_order.iter().enumerate() {
|
||||
let phi_dst = match loop_header_phi_info.get_carrier_phi(carrier_name) {
|
||||
Some(dst) => dst,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let mut chosen = None;
|
||||
for inst in new_block.instructions.iter().rev() {
|
||||
if let MirInstruction::Copy { dst, src } = inst {
|
||||
if *dst == phi_dst {
|
||||
chosen = Some(*src);
|
||||
if *src != *dst {
|
||||
if boundary.loop_var_name.as_deref() == Some(carrier_name) {
|
||||
loop_var_updated = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(val) = chosen {
|
||||
latch_args.push(val);
|
||||
} else if let Some(arg) = args.get(idx) {
|
||||
if boundary.loop_var_name.as_deref() == Some(carrier_name) && *arg != phi_dst {
|
||||
loop_var_updated = true;
|
||||
}
|
||||
latch_args.push(*arg);
|
||||
}
|
||||
}
|
||||
|
||||
if !loop_var_updated && boundary.loop_var_name.is_some() {
|
||||
// Skip latch record if we only see self-copies for loop var.
|
||||
} else {
|
||||
latch_incoming_recorder::record_if_backedge(
|
||||
tail_call_kind,
|
||||
Some(boundary),
|
||||
new_block_id,
|
||||
&latch_args,
|
||||
loop_header_phi_info,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
latch_incoming_recorder::record_if_backedge(
|
||||
tail_call_kind,
|
||||
boundary,
|
||||
new_block_id,
|
||||
args,
|
||||
loop_header_phi_info,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let actual_target = match tail_call_kind {
|
||||
TailCallKind::BackEdge => {
|
||||
log!(
|
||||
|
||||
@ -7,18 +7,21 @@ require_env || exit 2
|
||||
|
||||
# Test: Nested loop (3x3 iterations, sum = 9)
|
||||
FIXTURE="$NYASH_ROOT/apps/tests/phase1883_nested_minimal.hako"
|
||||
RUN_TIMEOUT_SECS=${RUN_TIMEOUT_SECS:-10}
|
||||
|
||||
if ! output=$(NYASH_DISABLE_PLUGINS=1 "$NYASH_BIN" --backend vm "$FIXTURE" 2>&1); then
|
||||
exit_code=$?
|
||||
log_error "phase1883_nested_minimal_vm: fixture failed to execute"
|
||||
echo "$output"
|
||||
set +e
|
||||
OUTPUT=$(timeout "$RUN_TIMEOUT_SECS" env NYASH_DISABLE_PLUGINS=1 HAKO_JOINIR_STRICT=1 "$NYASH_BIN" --backend vm "$FIXTURE" 2>&1)
|
||||
EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
if [ "$EXIT_CODE" -eq 124 ]; then
|
||||
log_error "phase1883_nested_minimal_vm: hakorune timed out (>${RUN_TIMEOUT_SECS}s)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check exit code == 9 (expected sum result)
|
||||
exit_code=$?
|
||||
if [ "$exit_code" != "9" ]; then
|
||||
log_error "phase1883_nested_minimal_vm: expected exit code 9, got $exit_code"
|
||||
if [ "$EXIT_CODE" -ne 9 ]; then
|
||||
log_error "phase1883_nested_minimal_vm: expected exit code 9, got $EXIT_CODE"
|
||||
echo "$OUTPUT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user