docs/spec-compiler-part-3-dfg.md

← all documents
not measured7highlighted source passages8/8PBTs with runs13draft PRs
20.3%combined confidence estimate6 considered · 2 missing
How combined confidence is computed

max(0, 1 − sum of PBT residual-risk estimates). Uses each distinct available PBT’s latest-run estimate and its own sampling scope; PBTs without estimates are reported and omitted. An observed violation remains the suite result; when other PBTs have estimates, their estimate is still shown. Assumes no independence. This is a combined point estimate, not a statistical confidence bound or deployment reliability. Uncovered passages are outside its scope.

Missing confidence evidence (omitted from the estimate):

Code coverage+115lines+91branches9contributing files8/8PBTs measured

Coverage added over the baseline, grouped by PBT. Only files with gains appear below.

Source fileBaseline coverageBaseline + inputContributing input
…/lib/Dataflow/IR/DataflowGraphValidation.cppMS0V
1033/1540lines67.1%
571/1056branches54.1%
1033/1540lines67.1%+0
572/1056branches54.2%+1
+1 branchseed 6 · 52 → 30 linesOpen PBT Review PR
1 newly covered branch
740    return argument && argument.getOwner() == &graph.getBody().front() &&741           argument.getArgNumber() != 0 &&742           graph.getInputPortKind(argument.getArgNumber() - 1) ==false branch743               dataflow::GraphPortKind::Stream;744  }745
…/lib/Dataflow/IR/DataflowActorSemantics.cppMS0V
1089/1621lines67.2%
747/1330branches56.2%
1090/1621lines67.2%+1
751/1330branches56.5%+4
+1 line · +4 branchesseed 0 · 37 → 33 linesOpen PBT Review PR
+1 line · +4 branchesseed 43 · 42 → 39 linesOpen PBT reduced; no PR packaged yet
+1 line · +4 branchesseed 43 · 42 → 39 linesOpen PBT reduced; no PR packaged yet
1 newly covered line · 4 newly covered branches
283    return {};284  auto invariant = demux.getInput().getDefiningOp<dataflow::InvariantOp>();285  if (!invariant || invariant.getCond() != stream.getPhase() ||true branchtrue branch286      invariant.getOutput() != demux.getInput())287    return {};288  return invariant.getInit();289}⋮306    auto stream = demux.getSel().getDefiningOp<dataflow::StreamOp>();307    if (stream && demux.getSel() == stream.getPhase() &&308        result.getResultNumber() == 1 && unwrapPhaseProjection(value, stream))false branchfalse branch309      return;310  }
…/lib/Dataflow/IR/DataflowGraphCausality.cppMS0V
169/189lines89.4%
79/98branches80.6%
169/189lines89.4%+0
81/98branches82.7%+2
+1 branchseed 421 · 90 → 90 linesOpen PBT Review PR
2 newly covered branches
2526  bool operator==(const CausalConstraintTransition &other) const {27    return parent == other.parent && selector == other.selector &&false branch28           lane == other.lane;false branch29  }30};
…/lib/Dataflow/IR/DataflowGraphValidation.cppMS0V
1033/1540lines67.1%
571/1056branches54.1%
1041/1540lines67.6%+8
579/1056branches54.8%+8
+4 lines · +2 branchesseed 421 · 90 → 90 linesOpen PBT Review PR
+8 lines · +8 branchesseed 43 · 42 → 39 linesOpen PBT reduced; no PR packaged yet
+8 lines · +8 branchesseed 43 · 42 → 39 linesOpen PBT reduced; no PR packaged yet
8 newly covered lines · 8 newly covered branches
254    auto it = findEquivalentSelector(selectorLanes, selector);255    bool inserted = it == selectorLanes.end();256    if (!inserted) {true branch257      if (it->second != lane)false branchtrue branch258        return false;259    } else {260      selectorLanes.try_emplace(selector, lane);261    }262    llvm::scope_exit cleanup([&] {263      if (inserted)false branch264        selectorLanes.erase(selector);265    });⋮302        if (!*closes)303          continue;304        if (!coversInLane(input, mux.getSel(), lane))true branch305          return false;306      }307      if (conditional)⋮632    if (known != sharedState->exactOne.end())633      return known->second;634    if (!sharedState->exactOneActive.insert(query).second)true branch635      return false;636    bool result = computeExactOne(value);637    sharedState->exactOneActive.erase(query);⋮1121    };11221123    if (auto stream = childPhase.getDefiningOp<dataflow::StreamOp>()) {false branch1124      if (childPhase != stream.getPhase() || !assumeExact(stream.getInit()) ||1125          !assumeExact(stream.getLimit()) || !assumeExact(stream.getStep()))1126        return false;1127    } else {1128      llvm::SmallVector<dataflow::CarryOp, 4> carries;1129      graphIndex->collectCarries(childPhase, carries);1130      if (carries.empty())false branch1131        return false;1132    }11331134    llvm::SmallVector<mlir::Value, 4> inputs;
…/lib/Dataflow/IR/OperationSchema.cppMS0V
274/716lines38.3%
127/350branches36.3%
274/716lines38.3%+0
129/350branches36.9%+2
+1 branchseed 0 · 37 → 33 linesOpen PBT Review PR
+2 branchesseed 421 · 90 → 90 linesOpen PBT Review PR
+1 branchseed 43 · 42 → 39 linesOpen PBT reduced; no PR packaged yet
+1 branchseed 43 · 42 → 39 linesOpen PBT reduced; no PR packaged yet
2 newly covered branches
728      lhsOp->getNumRegions() != 0 || rhsOp->getNumRegions() != 0 ||729      lhsOp->getNumSuccessors() != 0 || rhsOp->getNumSuccessors() != 0 ||730      !mlir::isPure(lhsOp) || !mlir::isPure(rhsOp) ||true branch731      lhsOp->getNumOperands() != rhsOp->getNumOperands())732    return false;⋮736      dataflow::operationSchemaOf(rhsOp);737  if (!lhsSchema || lhsSchema != rhsSchema ||738      dataflow::actorKind(*lhsSchema) !=true branch739          dataflow::CanonicalDataflowActorKind::Compute ||740      !dataflow::isDeterministic(*lhsSchema))741    return false;
…/lib/Frontend/Lowering/GraphParallelLowering.cppMS0V
651/1243lines52.4%
284/786branches36.1%
698/1243lines56.2%+47
316/786branches40.2%+32
+47 lines · +32 branchesseed 421 · 90 → 90 linesOpen PBT Review PR
+5 lines · +5 branchesseed 43 · 42 → 39 linesOpen PBT reduced; no PR packaged yet
+5 lines · +5 branchesseed 43 · 42 → 39 linesOpen PBT reduced; no PR packaged yet
47 newly covered lines · 32 newly covered branches
424425  std::optional<LinearExpression> build(::mlir::Value value) {426    if (auto found = cache.find(value); found != cache.end())true branch427      return found->second;428    if (failed.contains(value) || !active.insert(value).second)429      return std::nullopt;⋮476    }477478    if (auto add = ::llvm::dyn_cast<::mlir::arith::AddIOp>(def))true branch479      return finish(combine(add.getLhs(), add.getRhs(), /*subtract=*/false));480    if (auto sub = ::llvm::dyn_cast<::mlir::arith::SubIOp>(def))481      return finish(combine(sub.getLhs(), sub.getRhs(), /*subtract=*/true));482    if (auto mul = ::llvm::dyn_cast<::mlir::arith::MulIOp>(def))true branch483      return finish(multiply(mul.getLhs(), mul.getRhs()));484    if (auto cast = ::llvm::dyn_cast<::mlir::arith::IndexCastOp>(def))485      return finish(projectIndexCast(cast.getIn(), cast.getType(), false));⋮580581  void add(LinearExpression &target, const LinearExpression &source,582           bool subtract) {583    target.constant = subtract ? target.constant - source.constantfalse branch584                               : target.constant + source.constant;585    for (unsigned index = 0; index < target.lanes.size(); ++index)false branchtrue branch586      target.lanes[index] = subtractfalse branch587                                ? target.lanes[index] - source.lanes[index]588                                : target.lanes[index] + source.lanes[index];589    for (const auto &[symbol, coefficient] : source.symbols)false branch590      addSymbol(target, symbol, subtract ? -coefficient : coefficient);591    for (const auto &[lane, coefficient] : source.descendantLanes)false branch592      addCoefficient(target.descendantLanes, lane,593                     subtract ? -coefficient : coefficient);594  }595596  std::optional<LinearExpression> combine(::mlir::Value lhs, ::mlir::Value rhs,597                                          bool subtract) {598    auto left = build(lhs);599    auto right = build(rhs);600    if (!left || !right || !left->transforms.empty() ||false branchfalse branchfalse branch601        !right->transforms.empty())false branch602      return std::nullopt;603    add(*left, *right, subtract);604    return left;605  }606607  std::optional<LinearExpression> multiply(::mlir::Value lhs,608                                           ::mlir::Value rhs) {609    auto left = build(lhs);610    auto right = build(rhs);611    if (!left || !right || !left->transforms.empty() ||false branchfalse branchfalse branch612        !right->transforms.empty())false branch613      return std::nullopt;614615    auto isConstant = [](const LinearExpression &expression) {616      return expression.symbols.empty() && expression.descendantLanes.empty() &&true branchtrue branch617             ::llvm::all_of(expression.lanes, [](const ::llvm::APInt &value) {false branchtrue branch618               return value.isZero();619             });620    };621    if (!isConstant(*left) && !isConstant(*right))false branchtrue branch622      return std::nullopt;623    if (!isConstant(*left))true branch624      std::swap(left, right);625626    ::llvm::APInt scale = left->constant;627    right->constant *= scale;628    for (::llvm::APInt &coefficient : right->lanes)false branchtrue branch629      coefficient *= scale;630    for (auto &[symbol, coefficient] : right->symbols) {false branch631      (void)symbol;632      coefficient *= scale;633    }634    for (auto &[lane, coefficient] : right->descendantLanes) {false branch635      (void)lane;636      coefficient *= scale;637    }638    return right;639  }640641  bool dependsOnLane(::mlir::Value value) {⋮1312            SeenAddress &previous = found->second;1313            bool differentLane =1314                previous.firstLane != currentLane || previous.multipleLanes;false branchfalse branch1315            if (differentLane && (previous.writes || access.access->writes) &&false branch1316                (!previous.allAtomic || !access.access->atomic)) {1317              overlap = true;1318              return false;1319            }1320            if (previous.firstLane != currentLane)false branch1321              previous.multipleLanes = true;1322            previous.writes |= access.access->writes;1323            previous.allAtomic &= access.access->atomic;1324          }1325          return true;1326        });
…/lib/Frontend/Lowering/LowerGraphConstantsPass.cppMS0V
80/83lines96.4%
18/24branches75.0%
80/83lines96.4%+0
19/24branches79.2%+1
+1 branchseed 421 · 90 → 90 linesOpen PBT Review PR
1 newly covered branch
68  graph.getBody().walk([&](::mlir::Operation *op) {69    auto cst = ::mlir::dyn_cast<::mlir::arith::ConstantOp>(op);70    if (cst && !cst.use_empty())false branch71      targets.push_back(ScalarSource{cst.getOperation(), cst.getValue()});72  });
…/lib/Dataflow/IR/DataflowActorSemantics.cppMS0V
1089/1621lines67.2%
747/1330branches56.2%
1090/1621lines67.2%+1
750/1330branches56.4%+3
+1 line · +3 branchesseed 109 · 57 → 57 linesOpen PBT Review PR
+1 line · +3 branchesseed 39 · 65 → 58 linesOpen PBT Review PR
+1 line · +3 branchesseed 76 · 65 → 60 linesOpen PBT Review PR
+1 line · +3 branchesseed 230 · 64 → 63 linesOpen PBT Review PR
1 newly covered line · 3 newly covered branches
283    return {};284  auto invariant = demux.getInput().getDefiningOp<dataflow::InvariantOp>();285  if (!invariant || invariant.getCond() != stream.getPhase() ||true branchtrue branch286      invariant.getOutput() != demux.getInput())287    return {};288  return invariant.getInit();289}⋮306    auto stream = demux.getSel().getDefiningOp<dataflow::StreamOp>();307    if (stream && demux.getSel() == stream.getPhase() &&308        result.getResultNumber() == 1 && unwrapPhaseProjection(value, stream))false branch309      return;310  }
…/lib/Dataflow/IR/DataflowGraphValidation.cppMS0V
1033/1540lines67.1%
571/1056branches54.1%
1038/1540lines67.4%+5
575/1056branches54.5%+4
+1 branchseed 39 · 65 → 58 linesOpen PBT Review PR
+5 lines · +3 branchesseed 76 · 65 → 60 linesOpen PBT Review PR
+4 lines · +2 branchesseed 230 · 64 → 63 linesOpen PBT Review PR
+1 line · +2 branchesseed 335 · 65 → 62 linesOpen PBT Review PR
5 newly covered lines · 4 newly covered branches
711712  void inheritAssumptions(const GraphCardinalityAnalysis &parent) {713    for (mlir::Value value : parent.exactOneAssumptions)true branch714      insertExactOneAssumption(value);715    for (mlir::Value value : parent.alignedCarryAssumptions)716      insertAlignedCarryAssumption(value);⋮1121    };11221123    if (auto stream = childPhase.getDefiningOp<dataflow::StreamOp>()) {false branch1124      if (childPhase != stream.getPhase() || !assumeExact(stream.getInit()) ||1125          !assumeExact(stream.getLimit()) || !assumeExact(stream.getStep()))1126        return false;1127    } else {1128      llvm::SmallVector<dataflow::CarryOp, 4> carries;1129      graphIndex->collectCarries(childPhase, carries);1130      if (carries.empty())false branch1131        return false;1132    }11331134    llvm::SmallVector<mlir::Value, 4> inputs;⋮1435    graphIndex->collectCarries(phase, phaseCarries);1436    for (dataflow::CarryOp carry : phaseCarries)1437      if (isExactOne(carry.getInit()))false branch1438        carries.push_back(carry);1439  }
…/lib/Dataflow/IR/OperationSchema.cppMS0V
274/716lines38.3%
127/350branches36.3%
274/716lines38.3%+0
128/350branches36.6%+1
+1 branchseed 39 · 65 → 58 linesOpen PBT Review PR
+1 branchseed 76 · 65 → 60 linesOpen PBT Review PR
+1 branchseed 230 · 64 → 63 linesOpen PBT Review PR
1 newly covered branch
736      dataflow::operationSchemaOf(rhsOp);737  if (!lhsSchema || lhsSchema != rhsSchema ||738      dataflow::actorKind(*lhsSchema) !=true branch739          dataflow::CanonicalDataflowActorKind::Compute ||740      !dataflow::isDeterministic(*lhsSchema))741    return false;
…/lib/Dataflow/IR/DataflowDialect.cppMS1V
22/32lines68.8%
4/10branches40.0%
25/32lines78.1%+3
7/10branches70.0%+3
+3 lines · +3 branchesseed 0 · 18 → 16 linesOpen PBT Review PR
+3 lines · +3 branchesseed 0 · 18 → 16 linesOpen PBT Review PR
+3 lines · +3 branchesseed 135 · 24 → 22 linesOpen PBT Review PR
3 newly covered lines · 3 newly covered branches
24                         std::optional<uint64_t> workItemArgOrdinal) {25  switch (kind) {26  case ThreadDomainKind::DenseRectangular:false branch27    if (workItemArgOrdinal)28      return emitError()⋮30                "ordinal";31    return success();32  case ThreadDomainKind::DynamicWork:true branch33    if (!workItemArgOrdinal)false branch34      return emitError()35             << "dynamic-work thread domain requires a work-item argument "36                "ordinal";37    return success();38  }39  return emitError() << "unknown thread domain kind";
…/lib/Dataflow/IR/DataflowFunctionLikeOps.cppMS1V
693/1097lines63.2%
264/592branches44.6%
747/1097lines68.1%+54
298/592branches50.3%+34
+54 lines · +34 branchesseed 0 · 18 → 16 linesOpen PBT Review PR
+54 lines · +34 branchesseed 0 · 18 → 16 linesOpen PBT Review PR
+54 lines · +34 branchesseed 135 · 24 → 22 linesOpen PBT Review PR
54 newly covered lines · 34 newly covered branches
102enum class ExtentExprKind { Unsupported, Constant, AddI, IndexCast };103104static bool isScalarNonzeroSignlessIntegerOrIndex(Type type) {105  if (isa<IndexType>(type))true branch106    return true;107  auto integerType = dyn_cast<IntegerType>(type);108  return integerType && integerType.isSignless() && integerType.getWidth() != 0;109}110111static unsigned getScalarIntegerBitWidth(Type type) {⋮115}116117static ExtentExprKind classifyExtentExpr(Operation *op) {118  if (op->getNumRegions() != 0 || op->getNumSuccessors() != 0)false branchfalse branch119    return ExtentExprKind::Unsupported;120121  if (isa<arith::ConstantOp>(op)) {true branch122    if (op->getNumOperands() != 0 || op->getNumResults() != 1)false branchfalse branch123      return ExtentExprKind::Unsupported;124    Type resultType = op->getResult(0).getType();125    auto value = dyn_cast_or_null<IntegerAttr>(126        cast<arith::ConstantOp>(op).getProperties().value);127    if (!isScalarNonzeroSignlessIntegerOrIndex(resultType) || !value ||false branchfalse branchfalse branch128        value.getType() != resultType)false branch129      return ExtentExprKind::Unsupported;130    return ExtentExprKind::Constant;131  }132133  if (isa<arith::AddIOp>(op)) {⋮170    SmallVector<Frame> stack;171    schedule(value, stack);172    while (!stack.empty()) {true branch173      Frame &frame = stack.back();174      if (constants.contains(frame.value)) {false branch175        active.erase(frame.value);176        stack.pop_back();⋮178      }179180      Operation *definingOp = cast<OpResult>(frame.value).getOwner();181      bool descended = false;182      while (frame.nextOperand < definingOp->getNumOperands()) {false branch183        Value operand = definingOp->getOperand(frame.nextOperand++);184        if (constants.contains(operand))⋮189        }190      }191      if (descended)false branch192        continue;193194      evaluateOperation(definingOp, frame.kind);195      active.erase(frame.value);196      stack.pop_back();197    }198199    return constants.lookup(value);⋮217218    auto result = dyn_cast<OpResult>(value);219    if (!result) {false branch220      constants.try_emplace(value, Attribute{});221      return false;222    }223224    Operation *definingOp = result.getOwner();225    ExtentExprKind kind = classifyExtentExpr(definingOp);226    if (kind == ExtentExprKind::Unsupported) {false branch227      cacheUnknown(definingOp);228      return false;229    }230    if (!active.insert(value).second) {false branch231      cacheUnknown(definingOp);232      return false;233    }234235    stack.push_back({value, 0, kind});236    return true;237  }238239  void evaluateOperation(Operation *op, ExtentExprKind kind) {240    if (kind == ExtentExprKind::Constant) {true branch241      constants.try_emplace(242          op->getResult(0),243          cast<IntegerAttr>(cast<arith::ConstantOp>(op).getProperties().value));244      return;245    }246247    auto operandConstant = [&](unsigned index) {⋮329          auto constant =330              dyn_cast_or_null<IntegerAttr>(evaluator.evaluate(extent));331          if (constant && constant.getValue().isNegative()) {true branchfalse branch332            launch.emitOpError("grid upper bound #")333                << index << " must be nonnegative";⋮471    if (parseOne())472      return failure();473    while (succeeded(parser.parseOptionalComma()))true branch474      if (parseOne())false branch475        return failure();476    return parser.parseRParen();⋮539      p << " iv (";540      for (size_t i = N + 1, e = entry->getNumArguments(); i < e; ++i) {541        if (i > N + 1)true branch542          p << ", ";543        p.printRegionArgument(entry->getArgument(i));544      }⋮567    return emitOpError("must not declare function results");568569  if (getDomain().getKind() == ThreadDomainKind::DynamicWork) {true branch570    uint64_t ordinal = *getDomain().getWorkItemArgOrdinal();571    ArrayRef<Type> inputs = getFunctionType().getInputs();572    if (ordinal >= inputs.size())false branch573      return emitOpError("dynamic-work item argument ordinal ")574             << ordinal << " is out of bounds for " << inputs.size()575             << " thread inputs";576    for (auto [index, type] : llvm::enumerate(inputs))false branchtrue branch577      if (DataflowDialect::containsChannelOrThreadToken(type))false branch578        return emitOpError("dynamic-work thread input #")579               << index << " must not contain a channel or thread token";580  }581  if (!ownsThreadLaunchExtentAnalysis(*this))582    return success();⋮654//===----------------------------------------------------------------------===//655656LogicalResult ThreadWaitOp::verify() {657  if ((*this)->getParentOfType<ThreadOp>() ||false branchfalse branch658      (*this)->getParentOfType<GraphOp>())false branch659    return emitOpError(660        "must appear outside any dataflow.thread or dataflow.graph "661        "definition");662663  for (auto [index, token] : llvm::enumerate(getAsyncDependencies()))false branchtrue branch664    if (!token.getDefiningOp<ThreadLaunchOp>())false branch665      return emitOpError("operand #") << index666                                      << " must be produced directly by "667                                         "dataflow.thread.launch";668  return success();669}670671//===----------------------------------------------------------------------===//
…/lib/Frontend/Lowering/LowerForallToThreadPass.cppMS1V
30/34lines88.2%
2/4branches50.0%
30/34lines88.2%+0
3/4branches75.0%+1
+1 branchseed 0 · 18 → 16 linesOpen PBT Review PR
+1 branchseed 0 · 18 → 16 linesOpen PBT Review PR
+1 branchseed 135 · 24 → 22 linesOpen PBT Review PR
1 newly covered branch
45      rejected = true;46    });47    if (rejected)false branch48      signalPassFailure();49  }
…/lib/Dataflow/IR/DataflowFunctionLikeOps.cppMS1V
693/1097lines63.2%
264/592branches44.6%
734/1097lines66.9%+41
287/592branches48.5%+23
+41 lines · +23 branchesseed 2 · 14 → 12 linesOpen PBT Review PR
+41 lines · +23 branchesseed 2 · 14 → 12 linesOpen PBT Review PR
41 newly covered lines · 23 newly covered branches
102enum class ExtentExprKind { Unsupported, Constant, AddI, IndexCast };103104static bool isScalarNonzeroSignlessIntegerOrIndex(Type type) {105  if (isa<IndexType>(type))true branch106    return true;107  auto integerType = dyn_cast<IntegerType>(type);108  return integerType && integerType.isSignless() && integerType.getWidth() != 0;109}110111static unsigned getScalarIntegerBitWidth(Type type) {⋮115}116117static ExtentExprKind classifyExtentExpr(Operation *op) {118  if (op->getNumRegions() != 0 || op->getNumSuccessors() != 0)false branchfalse branch119    return ExtentExprKind::Unsupported;120121  if (isa<arith::ConstantOp>(op)) {true branch122    if (op->getNumOperands() != 0 || op->getNumResults() != 1)false branchfalse branch123      return ExtentExprKind::Unsupported;124    Type resultType = op->getResult(0).getType();125    auto value = dyn_cast_or_null<IntegerAttr>(126        cast<arith::ConstantOp>(op).getProperties().value);127    if (!isScalarNonzeroSignlessIntegerOrIndex(resultType) || !value ||false branchfalse branchfalse branch128        value.getType() != resultType)false branch129      return ExtentExprKind::Unsupported;130    return ExtentExprKind::Constant;131  }132133  if (isa<arith::AddIOp>(op)) {⋮170    SmallVector<Frame> stack;171    schedule(value, stack);172    while (!stack.empty()) {true branch173      Frame &frame = stack.back();174      if (constants.contains(frame.value)) {false branch175        active.erase(frame.value);176        stack.pop_back();⋮178      }179180      Operation *definingOp = cast<OpResult>(frame.value).getOwner();181      bool descended = false;182      while (frame.nextOperand < definingOp->getNumOperands()) {false branch183        Value operand = definingOp->getOperand(frame.nextOperand++);184        if (constants.contains(operand))⋮189        }190      }191      if (descended)false branch192        continue;193194      evaluateOperation(definingOp, frame.kind);195      active.erase(frame.value);196      stack.pop_back();197    }198199    return constants.lookup(value);⋮217218    auto result = dyn_cast<OpResult>(value);219    if (!result) {false branch220      constants.try_emplace(value, Attribute{});221      return false;222    }223224    Operation *definingOp = result.getOwner();225    ExtentExprKind kind = classifyExtentExpr(definingOp);226    if (kind == ExtentExprKind::Unsupported) {false branch227      cacheUnknown(definingOp);228      return false;229    }230    if (!active.insert(value).second) {false branch231      cacheUnknown(definingOp);232      return false;233    }234235    stack.push_back({value, 0, kind});236    return true;237  }238239  void evaluateOperation(Operation *op, ExtentExprKind kind) {240    if (kind == ExtentExprKind::Constant) {true branch241      constants.try_emplace(242          op->getResult(0),243          cast<IntegerAttr>(cast<arith::ConstantOp>(op).getProperties().value));244      return;245    }246247    auto operandConstant = [&](unsigned index) {⋮329          auto constant =330              dyn_cast_or_null<IntegerAttr>(evaluator.evaluate(extent));331          if (constant && constant.getValue().isNegative()) {true branchfalse branch332            launch.emitOpError("grid upper bound #")333                << index << " must be nonnegative";⋮471    if (parseOne())472      return failure();473    while (succeeded(parser.parseOptionalComma()))true branch474      if (parseOne())false branch475        return failure();476    return parser.parseRParen();⋮539      p << " iv (";540      for (size_t i = N + 1, e = entry->getNumArguments(); i < e; ++i) {541        if (i > N + 1)true branch542          p << ", ";543        p.printRegionArgument(entry->getArgument(i));544      }
…/lib/Frontend/Lowering/LowerForallToThreadPass.cppMS1V
30/34lines88.2%
2/4branches50.0%
31/34lines91.2%+1
4/4branches100.0%+2
+1 line · +2 branchesseed 2 · 14 → 12 linesOpen PBT Review PR
+1 line · +2 branchesseed 2 · 14 → 12 linesOpen PBT Review PR
1 newly covered line · 2 newly covered branches
37    bool rejected = false;38    getOperation().walk([&](::mlir::scf::ForallOp forall) {39      if (!forall->getParentOfType<::mlir::func::FuncOp>())true branch40        return;41      forall.emitError(42          "loom-lower-forall-to-thread: raw scf.forall has no recognized "⋮45      rejected = true;46    });47    if (rejected)false branch48      signalPassFailure();49  }
Files without added coverage and unmeasured PBTs
Source fileBaseline coverageBaseline + inputContributing input
derived from the coverage profile
Open PBT
not measurednot measuredno drafts yet
…/loom/include/Common/Artifact.hMS0V
13/37lines35.1%
3/18branches16.7%
13/37lines35.1%+0
3/18branches16.7%+0
Open PBT
…/include/Frontend/Lowering/StreamLoopAttrs.hMS0V
31/41lines75.6%
10/14branches71.4%
31/41lines75.6%+0
10/14branches71.4%+0
Open PBT
…/loom/lib/Common/DiagnosticVerbosity.cppMS0V
12/41lines29.3%
1/28branches3.6%
12/41lines29.3%+0
1/28branches3.6%+0
Open PBT
…/loom/lib/Common/IndexWidth.cppMS0V
63/84lines75.0%
28/42branches66.7%
63/84lines75.0%+0
28/42branches66.7%+0
Open PBT
…/loom/lib/Common/InvocationDiagnosticLog.cppMS0V
6/100lines6.0%
1/70branches1.4%
6/100lines6.0%+0
1/70branches1.4%+0
Open PBT
…/lib/Dataflow/IR/DataflowActorSemantics.cppMS0V
1089/1621lines67.2%
747/1330branches56.2%
1089/1621lines67.2%+0
747/1330branches56.2%+0
Open PBT
…/lib/Dataflow/IR/DataflowChannelOps.cppMS0V
59/88lines67.0%
13/38branches34.2%
59/88lines67.0%+0
13/38branches34.2%+0
Open PBT
…/lib/Dataflow/IR/DataflowDialect.cppMS0V
22/32lines68.8%
4/10branches40.0%
22/32lines68.8%+0
4/10branches40.0%+0
Open PBT
…/lib/Dataflow/IR/DataflowFunctionLikeOps.cppMS0V
693/1097lines63.2%
264/592branches44.6%
693/1097lines63.2%+0
264/592branches44.6%+0
Open PBT
…/lib/Dataflow/IR/DataflowGraphCausality.cppMS0V
169/189lines89.4%
79/98branches80.6%
169/189lines89.4%+0
79/98branches80.6%+0
Open PBT
…/lib/Dataflow/IR/DataflowMemoryContracts.cppMS0V
198/371lines53.4%
98/218branches45.0%
198/371lines53.4%+0
98/218branches45.0%+0
Open PBT
…/lib/Dataflow/IR/DataflowOps.cppMS0V
270/410lines65.9%
103/228branches45.2%
270/410lines65.9%+0
103/228branches45.2%+0
Open PBT
…/lib/Dataflow/IR/DataflowProgramValidation.cppMS0V
367/502lines73.1%
156/276branches56.5%
367/502lines73.1%+0
156/276branches56.5%+0
Open PBT
…/lib/Dataflow/IR/DataflowThreadCompletion.cppMS0V
288/361lines79.8%
148/218branches67.9%
288/361lines79.8%+0
148/218branches67.9%+0
Open PBT
…/lib/Dataflow/IR/DataflowVectorSemantics.cppMS0V
81/121lines66.9%
53/106branches50.0%
81/121lines66.9%+0
53/106branches50.0%+0
Open PBT
…/lib/Dataflow/IR/OperationSchema.cppMS0V
274/716lines38.3%
127/350branches36.3%
274/716lines38.3%+0
127/350branches36.3%+0
Open PBT
…/lib/Dataflow/IR/OperationSchemaCodecInternal.hMS0V
28/120lines23.3%
4/44branches9.1%
28/120lines23.3%+0
4/44branches9.1%+0
Open PBT
…/lib/Dataflow/IR/OperationSchemaTypeCodec.cppMS0V
85/516lines16.5%
55/374branches14.7%
85/516lines16.5%+0
55/374branches14.7%+0
Open PBT
…/lib/Dataflow/Transforms/DataflowRewritePass.cppMS0V
338/559lines60.5%
172/340branches50.6%
338/559lines60.5%+0
172/340branches50.6%+0
Open PBT
…/lib/Frontend/IR/LoomDialect.cppMS0V
6/6lines100.0%
branchesnot measured
6/6lines100.0%+0
branchesnot measured
Open PBT
…/lib/Frontend/IR/LoomOps.cppMS0V
120/184lines65.2%
71/112branches63.4%
120/184lines65.2%+0
71/112branches63.4%+0
Open PBT
…/lib/Frontend/Lowering/ExactMemRefLayout.cppMS0V
61/143lines42.7%
26/80branches32.5%
61/143lines42.7%+0
26/80branches32.5%+0
Open PBT
…/lib/Frontend/Lowering/ExpandGraphMemrefCopyPass.cppMS0V
79/90lines87.8%
20/22branches90.9%
79/90lines87.8%+0
20/22branches90.9%+0
Open PBT
…/lib/Frontend/Lowering/GraphIndexLowering.cppMS0V
260/288lines90.3%
129/168branches76.8%
260/288lines90.3%+0
129/168branches76.8%+0
Open PBT
…/lib/Frontend/Lowering/GraphParallelLowering.cppMS0V
651/1243lines52.4%
284/786branches36.1%
651/1243lines52.4%+0
284/786branches36.1%+0
Open PBT
…/lib/Frontend/Lowering/GraphRegionAdmission.cppMS0V
58/110lines52.7%
29/92branches31.5%
58/110lines52.7%+0
29/92branches31.5%+0
Open PBT
…/lib/Frontend/Lowering/GraphRegionLowering.cppMS0V
1416/1626lines87.1%
532/680branches78.2%
1416/1626lines87.1%+0
532/680branches78.2%+0
Open PBT
…/lib/Frontend/Lowering/GraphStreamBoundaryLowering.cppMS0V
934/1072lines87.1%
299/432branches69.2%
934/1072lines87.1%+0
299/432branches69.2%+0
Open PBT
…/lib/Frontend/Lowering/GraphStreamBoundaryLowering.hMS0V
4/4lines100.0%
4/4branches100.0%
4/4lines100.0%+0
4/4branches100.0%+0
Open PBT
…/lib/Frontend/Lowering/LowerForToGraphPass.cppMS0V
1033/1240lines83.3%
374/540branches69.3%
1033/1240lines83.3%+0
374/540branches69.3%+0
Open PBT
…/lib/Frontend/Lowering/LowerForallToThreadPass.cppMS0V
30/34lines88.2%
2/4branches50.0%
30/34lines88.2%+0
2/4branches50.0%+0
Open PBT
…/lib/Frontend/Lowering/LowerGraphConstantsPass.cppMS0V
80/83lines96.4%
18/24branches75.0%
80/83lines96.4%+0
18/24branches75.0%+0
Open PBT
…/lib/Frontend/Lowering/LowerGraphMemoryPass.cppMS0V
525/841lines62.4%
208/400branches52.0%
525/841lines62.4%+0
208/400branches52.0%+0
Open PBT
…/lib/Frontend/Lowering/Pipeline.cppMS0V
18/21lines85.7%
branchesnot measured
18/21lines85.7%+0
branchesnot measured
Open PBT
…/lib/Frontend/Lowering/RankedMemRefLowering.cppMS0V
60/133lines45.1%
27/94branches28.7%
60/133lines45.1%+0
27/94branches28.7%+0
Open PBT
…/lib/Frontend/Raising/DeduplicateSCFWhileStatePass.cppMS0V
13/135lines9.6%
0/60branches0.0%
13/135lines9.6%+0
0/60branches0.0%+0
Open PBT
…/lib/Frontend/Raising/LLVMArithToArithPass.cppMS0V
300/319lines94.0%
94/116branches81.0%
300/319lines94.0%+0
94/116branches81.0%+0
Open PBT
…/lib/Frontend/Raising/LLVMCfToCfPass.cppMS0V
77/80lines96.2%
8/8branches100.0%
77/80lines96.2%+0
8/8branches100.0%+0
Open PBT
…/lib/Frontend/Raising/LiftCFToSCFPass.cppMS0V
616/694lines88.8%
293/386branches75.9%
616/694lines88.8%+0
293/386branches75.9%+0
Open PBT
…/lib/Frontend/Raising/MaterializeFMulAddPass.cppMS0V
70/106lines66.0%
11/26branches42.3%
70/106lines66.0%+0
11/26branches42.3%+0
Open PBT
…/lib/Frontend/Raising/NormalizeLiftedSCFExitPass.cppMS0V
232/250lines92.8%
120/182branches65.9%
232/250lines92.8%+0
120/182branches65.9%+0
Open PBT
…/lib/Frontend/Raising/Pipeline.cppMS0V
10/19lines52.6%
branchesnot measured
10/19lines52.6%+0
branchesnot measured
Open PBT
…/lib/Frontend/Raising/SCFForToForallPass.cppMS0V
494/738lines66.9%
241/458branches52.6%
494/738lines66.9%+0
241/458branches52.6%+0
Open PBT
…/lib/Frontend/Raising/SCFWhileToForPass.cppMS0V
164/176lines93.2%
70/94branches74.5%
164/176lines93.2%+0
70/94branches74.5%+0
Open PBT
…/loom/tools/loom-raise-opt/loom-raise-opt.cppMS0V
12/12lines100.0%
branchesnot measured
12/12lines100.0%+0
branchesnot measured
Open PBT
…/loom/include/Common/Artifact.hMS0V
13/37lines35.1%
3/18branches16.7%
13/37lines35.1%+0
3/18branches16.7%+0
Open PBT
…/include/Dataflow/IR/OperationSchema.hMS0V
8/91lines8.8%
5/66branches7.6%
8/91lines8.8%+0
5/66branches7.6%+0
Open PBT
…/include/Frontend/Lowering/StreamLoopAttrs.hMS0V
31/41lines75.6%
10/14branches71.4%
31/41lines75.6%+0
10/14branches71.4%+0
Open PBT
…/loom/lib/Common/DiagnosticVerbosity.cppMS0V
12/41lines29.3%
1/28branches3.6%
12/41lines29.3%+0
1/28branches3.6%+0
Open PBT
…/loom/lib/Common/IndexWidth.cppMS0V
63/84lines75.0%
28/42branches66.7%
63/84lines75.0%+0
28/42branches66.7%+0
Open PBT
…/loom/lib/Common/InvocationDiagnosticLog.cppMS0V
6/100lines6.0%
1/70branches1.4%
6/100lines6.0%+0
1/70branches1.4%+0
Open PBT
…/lib/Dataflow/IR/DataflowChannelOps.cppMS0V
59/88lines67.0%
13/38branches34.2%
59/88lines67.0%+0
13/38branches34.2%+0
Open PBT
…/lib/Dataflow/IR/DataflowDialect.cppMS0V
22/32lines68.8%
4/10branches40.0%
22/32lines68.8%+0
4/10branches40.0%+0
Open PBT
…/lib/Dataflow/IR/DataflowFunctionLikeOps.cppMS0V
693/1097lines63.2%
264/592branches44.6%
693/1097lines63.2%+0
264/592branches44.6%+0
Open PBT
…/lib/Dataflow/IR/DataflowMemoryContracts.cppMS0V
198/371lines53.4%
98/218branches45.0%
198/371lines53.4%+0
98/218branches45.0%+0
Open PBT
…/lib/Dataflow/IR/DataflowOps.cppMS0V
270/410lines65.9%
103/228branches45.2%
270/410lines65.9%+0
103/228branches45.2%+0
Open PBT
…/lib/Dataflow/IR/DataflowProgramValidation.cppMS0V
367/502lines73.1%
156/276branches56.5%
367/502lines73.1%+0
156/276branches56.5%+0
Open PBT
…/lib/Dataflow/IR/DataflowSyncRendezvous.cppMS0V
21/21lines100.0%
6/12branches50.0%
21/21lines100.0%+0
6/12branches50.0%+0
Open PBT
…/lib/Dataflow/IR/DataflowThreadCompletion.cppMS0V
288/361lines79.8%
148/218branches67.9%
288/361lines79.8%+0
148/218branches67.9%+0
Open PBT
…/lib/Dataflow/IR/DataflowVectorSemantics.cppMS0V
81/121lines66.9%
53/106branches50.0%
81/121lines66.9%+0
53/106branches50.0%+0
Open PBT
…/lib/Dataflow/IR/OperationSchemaCodecInternal.hMS0V
28/120lines23.3%
4/44branches9.1%
28/120lines23.3%+0
4/44branches9.1%+0
Open PBT
…/lib/Dataflow/IR/OperationSchemaTypeCodec.cppMS0V
85/516lines16.5%
55/374branches14.7%
85/516lines16.5%+0
55/374branches14.7%+0
Open PBT
…/lib/Dataflow/Transforms/DataflowRewritePass.cppMS0V
338/559lines60.5%
172/340branches50.6%
338/559lines60.5%+0
172/340branches50.6%+0
Open PBT
…/lib/Frontend/Analysis/MemoryProvenance.cppMS0V
153/317lines48.3%
80/326branches24.5%
153/317lines48.3%+0
80/326branches24.5%+0
Open PBT
…/lib/Frontend/IR/LoomDialect.cppMS0V
6/6lines100.0%
branchesnot measured
6/6lines100.0%+0
branchesnot measured
Open PBT
…/lib/Frontend/IR/LoomOps.cppMS0V
120/184lines65.2%
71/112branches63.4%
120/184lines65.2%+0
71/112branches63.4%+0
Open PBT
…/lib/Frontend/Lowering/ExactMemRefLayout.cppMS0V
61/143lines42.7%
26/80branches32.5%
61/143lines42.7%+0
26/80branches32.5%+0
Open PBT
…/lib/Frontend/Lowering/ExpandGraphMemrefCopyPass.cppMS0V
79/90lines87.8%
20/22branches90.9%
79/90lines87.8%+0
20/22branches90.9%+0
Open PBT
…/lib/Frontend/Lowering/GraphIndexLowering.cppMS0V
260/288lines90.3%
129/168branches76.8%
260/288lines90.3%+0
129/168branches76.8%+0
Open PBT
…/lib/Frontend/Lowering/GraphRegionAdmission.cppMS0V
58/110lines52.7%
29/92branches31.5%
58/110lines52.7%+0
29/92branches31.5%+0
Open PBT
…/lib/Frontend/Lowering/GraphRegionLowering.cppMS0V
1416/1626lines87.1%
532/680branches78.2%
1416/1626lines87.1%+0
532/680branches78.2%+0
Open PBT
…/lib/Frontend/Lowering/GraphStreamBoundaryLowering.cppMS0V
934/1072lines87.1%
299/432branches69.2%
934/1072lines87.1%+0
299/432branches69.2%+0
Open PBT
…/lib/Frontend/Lowering/GraphStreamBoundaryLowering.hMS0V
4/4lines100.0%
4/4branches100.0%
4/4lines100.0%+0
4/4branches100.0%+0
Open PBT
…/lib/Frontend/Lowering/LowerForToGraphPass.cppMS0V
1033/1240lines83.3%
374/540branches69.3%
1033/1240lines83.3%+0
374/540branches69.3%+0
Open PBT
…/lib/Frontend/Lowering/LowerForallToThreadPass.cppMS0V
30/34lines88.2%
2/4branches50.0%
30/34lines88.2%+0
2/4branches50.0%+0
Open PBT
…/lib/Frontend/Lowering/LowerGraphMemoryPass.cppMS0V
525/841lines62.4%
208/400branches52.0%
525/841lines62.4%+0
208/400branches52.0%+0
Open PBT
…/lib/Frontend/Lowering/Pipeline.cppMS0V
18/21lines85.7%
branchesnot measured
18/21lines85.7%+0
branchesnot measured
Open PBT
…/lib/Frontend/Lowering/RankedMemRefLowering.cppMS0V
60/133lines45.1%
27/94branches28.7%
60/133lines45.1%+0
27/94branches28.7%+0
Open PBT
…/lib/Frontend/Raising/DeduplicateSCFWhileStatePass.cppMS0V
13/135lines9.6%
0/60branches0.0%
13/135lines9.6%+0
0/60branches0.0%+0
Open PBT
…/lib/Frontend/Raising/LLVMArithToArithPass.cppMS0V
300/319lines94.0%
94/116branches81.0%
300/319lines94.0%+0
94/116branches81.0%+0
Open PBT
…/lib/Frontend/Raising/LLVMCfToCfPass.cppMS0V
77/80lines96.2%
8/8branches100.0%
77/80lines96.2%+0
8/8branches100.0%+0
Open PBT
…/lib/Frontend/Raising/LiftCFToSCFPass.cppMS0V
616/694lines88.8%
293/386branches75.9%
616/694lines88.8%+0
293/386branches75.9%+0
Open PBT
…/lib/Frontend/Raising/MaterializeFMulAddPass.cppMS0V
70/106lines66.0%
11/26branches42.3%
70/106lines66.0%+0
11/26branches42.3%+0
Open PBT
…/lib/Frontend/Raising/NormalizeLiftedSCFExitPass.cppMS0V
232/250lines92.8%
120/182branches65.9%
232/250lines92.8%+0
120/182branches65.9%+0
Open PBT
…/lib/Frontend/Raising/Pipeline.cppMS0V
10/19lines52.6%
branchesnot measured
10/19lines52.6%+0
branchesnot measured
Open PBT
…/lib/Frontend/Raising/SCFForToForallPass.cppMS0V
494/738lines66.9%
241/458branches52.6%
494/738lines66.9%+0
241/458branches52.6%+0
Open PBT
…/lib/Frontend/Raising/SCFWhileToForPass.cppMS0V
164/176lines93.2%
70/94branches74.5%
164/176lines93.2%+0
70/94branches74.5%+0
Open PBT
…/loom/tools/loom-raise-opt/loom-raise-opt.cppMS0V
12/12lines100.0%
branchesnot measured
12/12lines100.0%+0
branchesnot measured
Open PBT
derived from the coverage profile
Open PBT
not measurednot measuredno drafts yet
…/loom/include/Common/Artifact.hMS0V
13/37lines35.1%
3/18branches16.7%
13/37lines35.1%+0
3/18branches16.7%+0
Open PBT
…/include/Dataflow/IR/OperationSchema.hMS0V
8/91lines8.8%
5/66branches7.6%
8/91lines8.8%+0
5/66branches7.6%+0
Open PBT
…/include/Frontend/Lowering/StreamLoopAttrs.hMS0V
31/41lines75.6%
10/14branches71.4%
31/41lines75.6%+0
10/14branches71.4%+0
Open PBT
…/loom/lib/Common/DiagnosticVerbosity.cppMS0V
12/41lines29.3%
1/28branches3.6%
12/41lines29.3%+0
1/28branches3.6%+0
Open PBT
…/loom/lib/Common/IndexWidth.cppMS0V
63/84lines75.0%
28/42branches66.7%
63/84lines75.0%+0
28/42branches66.7%+0
Open PBT
…/loom/lib/Common/InvocationDiagnosticLog.cppMS0V
6/100lines6.0%
1/70branches1.4%
6/100lines6.0%+0
1/70branches1.4%+0
Open PBT
…/lib/Dataflow/IR/DataflowChannelOps.cppMS0V
59/88lines67.0%
13/38branches34.2%
59/88lines67.0%+0
13/38branches34.2%+0
Open PBT
…/lib/Dataflow/IR/DataflowDialect.cppMS0V
22/32lines68.8%
4/10branches40.0%
22/32lines68.8%+0
4/10branches40.0%+0
Open PBT
…/lib/Dataflow/IR/DataflowFunctionLikeOps.cppMS0V
693/1097lines63.2%
264/592branches44.6%
693/1097lines63.2%+0
264/592branches44.6%+0
Open PBT
…/lib/Dataflow/IR/DataflowGraphCausality.cppMS0V
169/189lines89.4%
79/98branches80.6%
169/189lines89.4%+0
79/98branches80.6%+0
+2 branchesseed 109 · 57 → 57 linesOpen PBT Review PR
+1 branchseed 39 · 65 → 58 linesOpen PBT Review PR
…/lib/Dataflow/IR/DataflowMemoryContracts.cppMS0V
198/371lines53.4%
98/218branches45.0%
198/371lines53.4%+0
98/218branches45.0%+0
Open PBT
…/lib/Dataflow/IR/DataflowOps.cppMS0V
270/410lines65.9%
103/228branches45.2%
270/410lines65.9%+0
103/228branches45.2%+0
Open PBT
…/lib/Dataflow/IR/DataflowProgramValidation.cppMS0V
367/502lines73.1%
156/276branches56.5%
367/502lines73.1%+0
156/276branches56.5%+0
Open PBT
…/lib/Dataflow/IR/DataflowSyncRendezvous.cppMS0V
21/21lines100.0%
6/12branches50.0%
21/21lines100.0%+0
6/12branches50.0%+0
Open PBT
…/lib/Dataflow/IR/DataflowThreadCompletion.cppMS0V
288/361lines79.8%
148/218branches67.9%
288/361lines79.8%+0
148/218branches67.9%+0
Open PBT
…/lib/Dataflow/IR/DataflowVectorSemantics.cppMS0V
81/121lines66.9%
53/106branches50.0%
81/121lines66.9%+0
53/106branches50.0%+0
Open PBT
…/lib/Dataflow/IR/OperationSchemaCodecInternal.hMS0V
28/120lines23.3%
4/44branches9.1%
28/120lines23.3%+0
4/44branches9.1%+0
Open PBT
…/lib/Dataflow/IR/OperationSchemaTypeCodec.cppMS0V
85/516lines16.5%
55/374branches14.7%
85/516lines16.5%+0
55/374branches14.7%+0
Open PBT
…/lib/Dataflow/Transforms/DataflowRewritePass.cppMS0V
338/559lines60.5%
172/340branches50.6%
338/559lines60.5%+0
172/340branches50.6%+0
Open PBT
…/lib/Frontend/Analysis/MemoryProvenance.cppMS0V
153/317lines48.3%
80/326branches24.5%
153/317lines48.3%+0
80/326branches24.5%+0
Open PBT
…/lib/Frontend/IR/LoomDialect.cppMS0V
6/6lines100.0%
branchesnot measured
6/6lines100.0%+0
branchesnot measured
Open PBT
…/lib/Frontend/IR/LoomOps.cppMS0V
120/184lines65.2%
71/112branches63.4%
120/184lines65.2%+0
71/112branches63.4%+0
Open PBT
…/lib/Frontend/Lowering/ExactMemRefLayout.cppMS0V
61/143lines42.7%
26/80branches32.5%
61/143lines42.7%+0
26/80branches32.5%+0
Open PBT
…/lib/Frontend/Lowering/ExpandGraphMemrefCopyPass.cppMS0V
79/90lines87.8%
20/22branches90.9%
79/90lines87.8%+0
20/22branches90.9%+0
Open PBT
…/lib/Frontend/Lowering/GraphIndexLowering.cppMS0V
260/288lines90.3%
129/168branches76.8%
260/288lines90.3%+0
129/168branches76.8%+0
Open PBT
…/lib/Frontend/Lowering/GraphParallelLowering.cppMS0V
651/1243lines52.4%
284/786branches36.1%
651/1243lines52.4%+0
284/786branches36.1%+0
Open PBT
…/lib/Frontend/Lowering/GraphRegionAdmission.cppMS0V
58/110lines52.7%
29/92branches31.5%
58/110lines52.7%+0
29/92branches31.5%+0
Open PBT
…/lib/Frontend/Lowering/GraphRegionLowering.cppMS0V
1416/1626lines87.1%
532/680branches78.2%
1416/1626lines87.1%+0
532/680branches78.2%+0
Open PBT
…/lib/Frontend/Lowering/GraphStreamBoundaryLowering.cppMS0V
934/1072lines87.1%
299/432branches69.2%
934/1072lines87.1%+0
299/432branches69.2%+0
Open PBT
…/lib/Frontend/Lowering/GraphStreamBoundaryLowering.hMS0V
4/4lines100.0%
4/4branches100.0%
4/4lines100.0%+0
4/4branches100.0%+0
Open PBT
…/lib/Frontend/Lowering/LowerForToGraphPass.cppMS0V
1033/1240lines83.3%
374/540branches69.3%
1033/1240lines83.3%+0
374/540branches69.3%+0
Open PBT
…/lib/Frontend/Lowering/LowerForallToThreadPass.cppMS0V
30/34lines88.2%
2/4branches50.0%
30/34lines88.2%+0
2/4branches50.0%+0
Open PBT
…/lib/Frontend/Lowering/LowerGraphConstantsPass.cppMS0V
80/83lines96.4%
18/24branches75.0%
80/83lines96.4%+0
18/24branches75.0%+0
+1 branchseed 109 · 57 → 57 linesOpen PBT Review PR
…/lib/Frontend/Lowering/LowerGraphMemoryPass.cppMS0V
525/841lines62.4%
208/400branches52.0%
525/841lines62.4%+0
208/400branches52.0%+0
Open PBT
…/lib/Frontend/Lowering/Pipeline.cppMS0V
18/21lines85.7%
branchesnot measured
18/21lines85.7%+0
branchesnot measured
Open PBT
…/lib/Frontend/Lowering/RankedMemRefLowering.cppMS0V
60/133lines45.1%
27/94branches28.7%
60/133lines45.1%+0
27/94branches28.7%+0
Open PBT
…/lib/Frontend/Raising/DeduplicateSCFWhileStatePass.cppMS0V
13/135lines9.6%
0/60branches0.0%
13/135lines9.6%+0
0/60branches0.0%+0
Open PBT
…/lib/Frontend/Raising/LLVMArithToArithPass.cppMS0V
300/319lines94.0%
94/116branches81.0%
300/319lines94.0%+0
94/116branches81.0%+0
Open PBT
…/lib/Frontend/Raising/LLVMCfToCfPass.cppMS0V
77/80lines96.2%
8/8branches100.0%
77/80lines96.2%+0
8/8branches100.0%+0
Open PBT
…/lib/Frontend/Raising/LiftCFToSCFPass.cppMS0V
616/694lines88.8%
293/386branches75.9%
616/694lines88.8%+0
293/386branches75.9%+0
Open PBT
…/lib/Frontend/Raising/MaterializeFMulAddPass.cppMS0V
70/106lines66.0%
11/26branches42.3%
70/106lines66.0%+0
11/26branches42.3%+0
Open PBT
…/lib/Frontend/Raising/NormalizeLiftedSCFExitPass.cppMS0V
232/250lines92.8%
120/182branches65.9%
232/250lines92.8%+0
120/182branches65.9%+0
Open PBT
…/lib/Frontend/Raising/Pipeline.cppMS0V
10/19lines52.6%
branchesnot measured
10/19lines52.6%+0
branchesnot measured
Open PBT
…/lib/Frontend/Raising/SCFForToForallPass.cppMS0V
494/738lines66.9%
241/458branches52.6%
494/738lines66.9%+0
241/458branches52.6%+0
Open PBT
…/lib/Frontend/Raising/SCFWhileToForPass.cppMS0V
164/176lines93.2%
70/94branches74.5%
164/176lines93.2%+0
70/94branches74.5%+0
Open PBT
…/loom/tools/loom-raise-opt/loom-raise-opt.cppMS0V
12/12lines100.0%
branchesnot measured
12/12lines100.0%+0
branchesnot measured
Open PBT
derived from the coverage profile
Open PBT
not measurednot measuredno drafts yet
…/lib/Dataflow/IR/DataflowChannelOps.cppMS1V
59/88lines67.0%
13/38branches34.2%
59/88lines67.0%+0
13/38branches34.2%+0
Open PBT
…/lib/Dataflow/IR/OperationSchema.cppMS1V
274/716lines38.3%
127/350branches36.3%
274/716lines38.3%+0
127/350branches36.3%+0
Open PBT
…/lib/Dataflow/Transforms/DataflowRewritePass.cppMS1V
338/559lines60.5%
172/340branches50.6%
338/559lines60.5%+0
172/340branches50.6%+0
Open PBT
…/lib/Frontend/Lowering/ExpandGraphMemrefCopyPass.cppMS1V
79/90lines87.8%
20/22branches90.9%
79/90lines87.8%+0
20/22branches90.9%+0
Open PBT
…/lib/Frontend/Lowering/LowerForToGraphPass.cppMS1V
1033/1240lines83.3%
374/540branches69.3%
1033/1240lines83.3%+0
374/540branches69.3%+0
Open PBT
…/lib/Frontend/Lowering/LowerGraphConstantsPass.cppMS1V
80/83lines96.4%
18/24branches75.0%
80/83lines96.4%+0
18/24branches75.0%+0
Open PBT
…/lib/Frontend/Lowering/LowerGraphMemoryPass.cppMS1V
525/841lines62.4%
208/400branches52.0%
525/841lines62.4%+0
208/400branches52.0%+0
Open PBT
…/lib/Frontend/Lowering/Pipeline.cppMS1V
18/21lines85.7%
branchesnot measured
18/21lines85.7%+0
branchesnot measured
Open PBT
…/lib/Frontend/Raising/DeduplicateSCFWhileStatePass.cppMS1V
13/135lines9.6%
0/60branches0.0%
13/135lines9.6%+0
0/60branches0.0%+0
Open PBT
…/lib/Frontend/Raising/LLVMArithToArithPass.cppMS1V
300/319lines94.0%
94/116branches81.0%
300/319lines94.0%+0
94/116branches81.0%+0
Open PBT
…/lib/Frontend/Raising/LLVMCfToCfPass.cppMS1V
77/80lines96.2%
8/8branches100.0%
77/80lines96.2%+0
8/8branches100.0%+0
Open PBT
…/lib/Frontend/Raising/LiftCFToSCFPass.cppMS1V
616/694lines88.8%
293/386branches75.9%
616/694lines88.8%+0
293/386branches75.9%+0
Open PBT
…/lib/Frontend/Raising/MaterializeFMulAddPass.cppMS1V
70/106lines66.0%
11/26branches42.3%
70/106lines66.0%+0
11/26branches42.3%+0
Open PBT
…/lib/Frontend/Raising/NormalizeLiftedSCFExitPass.cppMS1V
232/250lines92.8%
120/182branches65.9%
232/250lines92.8%+0
120/182branches65.9%+0
Open PBT
…/lib/Frontend/Raising/Pipeline.cppMS1V
10/19lines52.6%
branchesnot measured
10/19lines52.6%+0
branchesnot measured
Open PBT
…/lib/Frontend/Raising/SCFForToForallPass.cppMS1V
494/738lines66.9%
241/458branches52.6%
494/738lines66.9%+0
241/458branches52.6%+0
Open PBT
…/lib/Frontend/Raising/SCFWhileToForPass.cppMS1V
164/176lines93.2%
70/94branches74.5%
164/176lines93.2%+0
70/94branches74.5%+0
Open PBT
…/loom/tools/loom-raise-opt/loom-raise-opt.cppMS1V
12/12lines100.0%
branchesnot measured
12/12lines100.0%+0
branchesnot measured
Open PBT
…/lib/Dataflow/IR/DataflowDialect.cppMS1V
22/32lines68.8%
4/10branches40.0%
22/32lines68.8%+0
4/10branches40.0%+0
Open PBT
…/lib/Dataflow/IR/OperationSchema.cppMS1V
274/716lines38.3%
127/350branches36.3%
274/716lines38.3%+0
127/350branches36.3%+0
Open PBT
…/lib/Dataflow/Transforms/DataflowRewritePass.cppMS1V
338/559lines60.5%
172/340branches50.6%
338/559lines60.5%+0
172/340branches50.6%+0
Open PBT
…/lib/Frontend/Lowering/ExpandGraphMemrefCopyPass.cppMS1V
79/90lines87.8%
20/22branches90.9%
79/90lines87.8%+0
20/22branches90.9%+0
Open PBT
…/lib/Frontend/Lowering/LowerForToGraphPass.cppMS1V
1033/1240lines83.3%
374/540branches69.3%
1033/1240lines83.3%+0
374/540branches69.3%+0
Open PBT
…/lib/Frontend/Lowering/LowerGraphConstantsPass.cppMS1V
80/83lines96.4%
18/24branches75.0%
80/83lines96.4%+0
18/24branches75.0%+0
Open PBT
…/lib/Frontend/Lowering/LowerGraphMemoryPass.cppMS1V
525/841lines62.4%
208/400branches52.0%
525/841lines62.4%+0
208/400branches52.0%+0
Open PBT
…/lib/Frontend/Lowering/Pipeline.cppMS1V
18/21lines85.7%
branchesnot measured
18/21lines85.7%+0
branchesnot measured
Open PBT
…/lib/Frontend/Raising/DeduplicateSCFWhileStatePass.cppMS1V
13/135lines9.6%
0/60branches0.0%
13/135lines9.6%+0
0/60branches0.0%+0
Open PBT
…/lib/Frontend/Raising/LLVMArithToArithPass.cppMS1V
300/319lines94.0%
94/116branches81.0%
300/319lines94.0%+0
94/116branches81.0%+0
Open PBT
…/lib/Frontend/Raising/LLVMCfToCfPass.cppMS1V
77/80lines96.2%
8/8branches100.0%
77/80lines96.2%+0
8/8branches100.0%+0
Open PBT
…/lib/Frontend/Raising/LiftCFToSCFPass.cppMS1V
616/694lines88.8%
293/386branches75.9%
616/694lines88.8%+0
293/386branches75.9%+0
Open PBT
…/lib/Frontend/Raising/MaterializeFMulAddPass.cppMS1V
70/106lines66.0%
11/26branches42.3%
70/106lines66.0%+0
11/26branches42.3%+0
Open PBT
…/lib/Frontend/Raising/NormalizeLiftedSCFExitPass.cppMS1V
232/250lines92.8%
120/182branches65.9%
232/250lines92.8%+0
120/182branches65.9%+0
Open PBT
…/lib/Frontend/Raising/Pipeline.cppMS1V
10/19lines52.6%
branchesnot measured
10/19lines52.6%+0
branchesnot measured
Open PBT
…/lib/Frontend/Raising/SCFForToForallPass.cppMS1V
494/738lines66.9%
241/458branches52.6%
494/738lines66.9%+0
241/458branches52.6%+0
Open PBT
…/lib/Frontend/Raising/SCFWhileToForPass.cppMS1V
164/176lines93.2%
70/94branches74.5%
164/176lines93.2%+0
70/94branches74.5%+0
Open PBT
…/loom/tools/loom-raise-opt/loom-raise-opt.cppMS1V
12/12lines100.0%
branchesnot measured
12/12lines100.0%+0
branchesnot measured
Open PBT
–

docs/spec-compiler-part-3-dfg.md · pinned revision 48615bc5925ef4b9db8b4550b5d4322933cf4b7b

1

Loom Compiler Part 3: SCF to DFG

3

This document specifies the third compiler part of the Loom front-end: mechanically lowering selected SCF-stage accelerator regions into the initial Canonical Dataflow Program, Loom's final target-independent software IR.

7

Translation and recovery through the initial SCF-stage IR are mechanical. SCF-to-SCF transformation is the primary compiler optimization and DSE domain; its selected immutable Structured Program Candidate must already materialize the schedule, parallel, vector, reduction, memory-overlap, AccCore ownership, and SpatialCore ownership decisions consumed here. Part 3 does not choose or repair those decisions.

14

The target Part 3 dataflow surface uses module-scope, Symbol-bearing, function-like definitions for both dataflow.thread and dataflow.graph. Execution is materialized only by dataflow.thread.launch and dataflow.graph.launch.MS0V699.7%1 · MS0V6 selected-output · selected outputproperty — checked on every compiled pairSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:037b2351ec38125b2cd5ae56 with its governing context; report any stage at5000 generated · 5000 pairedtrace evidence 1000 samples / 3 classesopen PBT → Graph control

18

ports are explicit in the current graph ABI: ctrl_in and launch-facing done_out are invocation protocol endpoints represented at every launch site, not application payload slots in the dataflow.graph function type. The graph body does not return done_out; its structural dataflow.graph.return.complete frontier is the unique authority from which

23

the launch result is derived. Part 3 consumes each explicit loom.spatial_region inside its owning dataflow.thread and publishes the corresponding graph definition and launch only after complete conversion and native finalization succeed.MS1V5 · MS1V linked-input-162 · linked input 162generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:cb03cffe9a6fce2f72e3c64f with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V64 · MS0V6 linked-input-162 · linked input 162generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:037b2351ec38125b2cd5ae56 with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V33 · MS0V3 linked-input-162 · linked input 162generator constraint — feeds the generated inputsMLIR passage 4: Graph bodies exclude calls and nested callables1000 generated · 824 paired · 176 rejectedopen PBT →MS0V22 · MS0V2 linked-input-162 · linked input 162generator constraint — feeds the generated inputsMLIR passage 2: Graph launch execution boundary5000 generated · 5000 pairedopen PBT →

27

The precise timing semantics of dataflow.stream, dataflow.carry, dataflow.invariant, and dataflow.gate are specified separately in docs/spec-dataflow-part-1-streaming.md. The precise firing semantics of dataflow.constant, dataflow.sync, dataflow.mux, and dataflow.demux are specified separately in docs/spec-dataflow-part-2-control.md.

34

The initial Canonical Dataflow Program may subsequently participate in its own typed, semantics-preserving Dataflow optimization lineage. Such a rewrite produces another immutable Canonical Dataflow Program and may use pure Dataflow or optional Fabric-aware Evaluation to rank candidates. It must not reselect a structured schedule or ownership boundary, and Fabric facts never become Canonical Dataflow semantics.

41

For a selected-Spatial scalar ScalarMath* actor, semantics include the exact SpecialMathAccuracyTier selected by the Structured candidate under docs/spec-compiler-part-2-scf.md. Mechanical lowering copies that owner-coded tier into the actor's closed semantic projection. Every D0-to-D* rewrite preserves it exactly; changing, dropping, or newly choosing the tier is not a semantics-preserving Dataflow transformation.

48

This document owns the target contract and the closed Canonical Dataflow rewrite catalog: IR boundaries, structured-control flattening, memory-dependence integration, rewrite equivalence and decision semantics, and verifier invariants. Pass decomposition, test layout, and maintenance sequencing are implementation choices and are not a second tracked specification.

55

Fabric realization and actor grouping are TechMapping concerns. Part 3 performs only structural eligibility and canonical graph publication inside an already established dataflow.thread; it does not assign a target or retain target-specific grouping in program IR. Absence of a realization on a particular Fabric is a Mapping result, not a graph-validity failure.

61

1. Scope and Contract

63

The compiler front-end is documented in four parts:

65
  • Part 1, source integration. LLVM IR plus optional typed Loom hints is the source-facing compiler contract. Any high-level language provider may participate by emitting valid LLVM IR; embedded clang for C / C++ is the first limited provider. Missing hints reduce available guidance but do not make otherwise valid provider input illegal.
  • Part 2, LLVM to initial SCF. LLVM/CFG-shaped input is mechanically raised and normalized into mixed-dialect initial SCF-stage MLIR. Imported LLVM callable envelopes and any operation without an exact standard equivalent remain LLVM dialect. The part recovers structured execution and preserves analysis inputs without selecting a QoR-distinct schedule, vector form, reduction strategy, or ownership boundary.
  • Part 3, SCF to DFG. This document. It consumes explicit loom.spatial_region candidates inside the selected Structured Program Candidate's dataflow.thread definitions and mechanically publishes dataflow.graph definitions plus dataflow.graph.launch ops at those candidate sites.
  • Part 4, logical domains and data views. The canonical dense-coordinate and dynamic-work domain ABI, work-item termination, source-IV reconstruction, and ordinary value or memory views derived from those domains (see docs/spec-compiler-part-4-partitioned-data.md).
87

Between Parts 2 and 3, SCF optimization and DSE produce the selected Structured Program Candidate. That domain owns all performance-distinct structured choices. Part 3 begins only after those choices and their typed ownership carriers are explicit.MS0V87 · MS0V8 linked-input-203 · linked input 203generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:d927678b3d8d4549df5bd40f with its governing context; report any stage attr5000 generated · 5000 pairedopen PBT →MS0V56 · MS0V5 linked-input-203 · linked input 203generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:f9b33846c2d82708b7bf1867 with its governing context; report any stage attr1000 generated · 964 paired · 36 rejectedopen PBT →

92

Input to graph extraction is an MLIR module containing module-scope dataflow.thread definitions. Every selected SpatialCore candidate is already materialized as a loom.spatial_region inside exactly one thread. Other thread body code remains InstructionCore-resident, including SCF-shaped code outside an explicit spatial boundary. Imported Host or InstructionCore code remains in its llvm.func envelope; genuinely standard-MLIR-native func.func callables may coexist in the module. Either callable is ownership-neutral and does not authorize graph creation through its signature, body shape, memory effects, or return convention.MS0V89 · MS0V8 linked-input-76 · linked input 76generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:d927678b3d8d4549df5bd40f with its governing context; report any stage attr5000 generated · 5000 pairedopen PBT →MS0V58 · MS0V5 linked-input-76 · linked input 76generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:f9b33846c2d82708b7bf1867 with its governing context; report any stage attr1000 generated · 964 paired · 36 rejectedopen PBT →

102

Output is an initial Canonical Dataflow Program: module-level llvm.func symbols for imported LLVM callables, any genuinely native func.func helpers, module-level dataflow.thread definitions reached by zero or more dataflow.thread.launch ops; and module-level dataflow.graph definitions reached by zero or more dataflow.graph.launch ops inside thread definitions. No scf.* op is left inside any dataflow.graph definition's body after successful graph-region lowering.MS0V5n/a10 · MS0V5 selected-output · selected outputproperty — checked on every compiled pairSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:f9b33846c2d82708b7bf1867 with its governing context; report any stage attr1000 generated · 964 paired · 36 rejectedtrace evidence 964 samples / 431 classesopen PBT →

110

The recursive lowering contract accepts arbitrary nesting of scf.if, source-sequential scf.for, scf.while, and fixed-width graph-owned scf.parallel or effect-form scf.forall. A graph-owned parallel op must have a compile-time fixed domain, and all facts needed to establish ownership, width, and cross-lane legality must be present in the current Structured Program Candidate's semantic IR and resolved lowering config. The lowerer re-proves those facts; lineage, cached analyses, and externalMS0V511 · MS0V5 linked-input-69 · linked input 69generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:f9b33846c2d82708b7bf1867 with its governing context; report any stage attr1000 generated · 964 paired · 36 rejectedopen PBT →MS0V812 · MS0V8 linked-input-69 · linked input 69generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:d927678b3d8d4549df5bd40f with its governing context; report any stage attr5000 generated · 5000 pairedopen PBT →

117

provenance cannot make an otherwise invalid candidate legal. Dynamic-width,MS0V511 · MS0V5 linked-input-69 · linked input 69generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:f9b33846c2d82708b7bf1867 with its governing context; report any stage attr1000 generated · 964 paired · 36 rejectedopen PBT →MS0V513 · MS0V5 linked-input-92 · linked input 92generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:f9b33846c2d82708b7bf1867 with its governing context; report any stage attr1000 generated · 964 paired · 36 rejectedopen PBT →MS0V812 · MS0V8 linked-input-69 · linked input 69generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:d927678b3d8d4549df5bd40f with its governing context; report any stage attr5000 generated · 5000 pairedopen PBT →MS0V814 · MS0V8 linked-input-92 · linked input 92generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:d927678b3d8d4549df5bd40f with its governing context; report any stage attr5000 generated · 5000 pairedopen PBT →

118

resource-mapped, and result or reduction forms fail before any graph is mutated; the graph owner does not infer ownership, serialization, unrolling, or reduction order. TheMS0V513 · MS0V5 linked-input-92 · linked input 92generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:f9b33846c2d82708b7bf1867 with its governing context; report any stage attr1000 generated · 964 paired · 36 rejectedopen PBT →MS0V814 · MS0V8 linked-input-92 · linked input 92generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:d927678b3d8d4549df5bd40f with its governing context; report any stage attr5000 generated · 5000 pairedopen PBT →

121

dataflow.thread.launch op carries the completion token and mapped-memory data transfer; the def remains a callable kernel body, not a tensor-result returning op. Memory dependence construction runs in the recursive graph owner using basic graph-local alias roots and per-partition write/read frontiers (see docs/spec-compiler-part-3-mem.md).

127

The Structured Transfer Algebra defines graph-owned parallel composition only after the Structured Program Candidate has materialized its P[] ownership and schedule form in semantic SCF. That fixed-domain SCF is the transient input representation for mechanical lowering. It is recursively replicated into static lanes and removed; no parallel control op or schedule record survives in canonical graph IR.MS0V817 · MS0V8 linked-input-176 · linked input 176generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:d927678b3d8d4549df5bd40f with its governing context; report any stage attr5000 generated · 5000 pairedopen PBT →MS0V878.7%16 · MS0V8 selected-output · selected outputproperty — checked on every compiled pairSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:d927678b3d8d4549df5bd40f with its governing context; report any stage attr5000 generated · 5000 pairedtrace evidence 1000 samples / 213 classesopen PBT →MS0V515 · MS0V5 linked-input-176 · linked input 176generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:f9b33846c2d82708b7bf1867 with its governing context; report any stage attr1000 generated · 964 paired · 36 rejectedopen PBT →

133

Graph candidate eligibility and atomic publication are governed by this document. TechMapping, SpatialMapping, and SystemMapping realization are outside this IR.

137

Canonical Dataflow Rewrite Catalog

139

This section is the sole normative owner of the closed Canonical Dataflow rewrite catalog, its decision wire, and the cross-rule equivalence boundary. Rule-specific documents may own a rule's detailed operation semantics, and the DSE specification owns enumeration and work accounting, but neither may add, remove, rename, or reorder a rewrite kind.

145

Every rewrite preserves, for all legal input, value, stream, and memory response traces under the abstract fair and resource-unbounded Dataflow execution semantics, the same externally ordered values and streams, visible memory behavior, completion and retirement, and termination or nontermination. It may change internal actor identities, topology, firing traces, and cycle count. A rewrite may neither introduce permanent deadlock nor permanently refuse an input accepted by its parent. Concrete Fabric capacity, routing, latency, arbitration, and physical deadlock remain Mapping or Evaluation facts and cannot weaken this software-equivalence contract.

155

The closed catalog is version 2.0:

157
Ordinal DataflowRewriteKind Normalized decision
0 SyncRendezvousRefactor exact root ActorRef and DirectToTree or TreeToDirect
1 PackUnpackRoundTripEliminate exact outer-adapter ActorRef
2 ParallelizeSerializeRoundTripEliminate exact outer serialize ActorRef
3 ElementwiseCardinalityCommute exact Compute ActorRef, complete cardinality-adapter ActorRef set, and MoveInside or MoveOutside
4 PureComputeFanoutRefactor Replicate with one exact Compute ActorRef, or Factor with the complete canonical Compute-replica ActorRef set
5 ActivationPreservingConstantFold exact Compute ActorRef
6 GraphDefinitionRefactor Split or Merge parameters below
7 ElementwiseVectorDecompose exact Compute ActorRef and LeadingChunk(C) or Scalarize
168

GraphDefinitionRefactor::Split carries one exact GraphRef and a canonical nonempty proper sorted-unique set of StaticGraphLaunchRef values that call that graph. The selected set's canonical sequence must be lexicographically smaller than its nonempty complement, so the two sides of one bipartition cannot encode two decisions. GraphDefinitionRefactor::Merge carries a canonically ordered pair of distinct GraphRef values. LeadingChunk(C) carries a positive u64 per-chunk leading extent; the detailed divisor and scalarization domain is owned by Explicit Elementwise Decomposition. The two decomposition modes are parameters of one rule kind because they use one operation-schema eligibility owner and one observable-equivalence contract. They are not two catalog extensions.

181

The cardinality-adapter set is nonempty, sorted, unique, and contains every source parallelize or serialize actor in the one connected shell crossed by the commute; a proper subset is not a match. A fanout Factor set is sorted, unique, contains at least two Compute actors, and names the complete coupled replica group selected by that decision. These sets are decision parameters rather than inferred matcher state because the same Compute actor or first replica can participate in more than one otherwise legal local match.

190

Every decision reference must belong to the exact parent Canonical Dataflow Artifact on its sole lineage edge. That parent is the semantic context for the decision payload, so parent-local references encode only their canonical EntityId and never repeat the ArtifactIdentity. One decision applies exactly one matched source pattern and publishes at most one child. Applying the same kind at two matches requires two lineage edges, so intermediate immutable candidates remain available to DSE. Match enumeration uses complete typed references and canonical decision bytes, never textual positions, symbol spelling, pointer order, or pass traversal order. A no-op publishes no child. Equal finalized semantic content deduplicates by ArtifactIdentity even when reached by different decision paths.

202

The decision schema identity is loom.dataflow_rewrite.decision, version 2.0. Its exact descriptor bytes are the ASCII bytes loom.dataflow_rewrite.decision.2.0 without a trailing zero byte. Canonical payload primitives are:

207
kind = u32be(kind_ordinal)
enum = u32be(owner_local_ordinal)
ref  = u64be(parent_local_EntityId)
refs = u64be(count), followed by the sorted unique ref values
214

Concatenation has no implicit tag, padding, terminator, or length other than the stated refs count. The exact payloads are:

217
Kind Exact left-to-right payload
SyncRendezvousRefactor kind, root_ref, direction
PackUnpackRoundTripEliminate kind, outer_adapter_ref
ParallelizeSerializeRoundTripEliminate kind, outer_serialize_ref
ElementwiseCardinalityCommute kind, compute_ref, adapter_refs, direction
PureComputeFanoutRefactor::Replicate kind, variant, compute_ref
PureComputeFanoutRefactor::Factor kind, variant, replica_refs
ActivationPreservingConstantFold kind, compute_ref
GraphDefinitionRefactor::Split kind, variant, graph_ref, launch_refs
GraphDefinitionRefactor::Merge kind, variant, lower_graph_ref, higher_graph_ref
ElementwiseVectorDecompose::LeadingChunk(C) kind, compute_ref, mode, u64be(C)
ElementwiseVectorDecompose::Scalarize kind, compute_ref, mode
231

The owner-local direction or variant ordinals are DirectToTree = 0, TreeToDirect = 1, MoveInside = 0, MoveOutside = 1, Replicate = 0, Factor = 1, Split = 0, Merge = 1, LeadingChunk = 0, and Scalarize = 1. Decoding requires exact length, known ordinals, parent ownership, canonical sets and pair order, semantic legality, and byte-for-byte re-encoding.

238

Canonical decision enumeration compares kind ordinal first. Within a kind it compares, in order: root then direction for kind 0; outer adapter for kinds 1 and 2; Compute, adapter-ref sequence, then direction for kind 3; variant then Compute or replica-ref sequence for kind 4; Compute for kind 5; variant then Graph and launch-ref sequence or Graph pair for kind 6; and Compute for kind 7. For the same kind-7 Compute, proper leading divisors are enumerated by descending C, followed by Scalarize when legal. Reference sequences use unsigned EntityId order and ordinary lexicographic sequence order, with a strict prefix ordered before its extension. This semantic enumeration order is not inferred from lexicographic payload-byte order; in particular, u64be(C) would order chunks in the wrong direction.

250

Version 1.0 admitted only PackUnpackRoundTripEliminate, ParallelizeSerializeRoundTripEliminate, and ActivationPreservingConstantFold as fixed kinds, plus chunk and scalarization as separate outer decision variants. Its fixed kinds carried no per-match anchor. It is incompatible and must be rejected rather than reinterpreted as this catalog.

258

The eight rules have these exact legality boundaries:

260
  • SyncRendezvousRefactor converts an N-way dataflow.sync, for N > 2, with exactly one externally live carried result to or from the rule's canonical binary rendezvous tree. The ordered input range is recursively split into contiguous halves with the left half receiving the extra element. Every internal node remains an independent canonical actor. A subtree that contains the original input corresponding to the sole live result carries that value; every other subtree carries the value of its lowest original input ordinal. An ancestor containing the live input therefore selects that descendant's carrier, while every original input remains a prerequisite even when its local result is dead. The reverse direction accepts only that exact carrier choice and tree, with no side use of an internal result. Only a maximal canonical tree root is a normalized reverse decision; a proper subtree of another recognized tree is not independently enumerated. This is an optional topology alternative, not mandatory wide-to-binary lowering. Graph lowering publishes a pure event join with more than four prerequisites directly in this canonical tree form when only its control carrier is live. The maximal reverse decision still exposes the equivalent wide rendezvous to later exploration.
  • PackUnpackRoundTripEliminate removes exact unpack(pack(vector)) or pack(unpack(bits)) round trips only when source and outer result have the same complete type, total bit width, lane order, and bit representation and the intermediate result has no side use. The composed registered exceptional-state projections must be identity over the source's complete proven value-state domain. The packed-scalar direction satisfies that requirement for its defined, poison, and undef states. The vector direction requires the source domain to contain only the homogeneous states admitted by the vector owner, commonly by proving every runtime vector token fully defined; a general mixed-lane vector is not eligible. The rule never inserts or implies a physical transport adapter.
  • ParallelizeSerializeRoundTripEliminate removes only exact serialize(parallelize(scalar stream)) compositions whose width, data and mask shape, phase connection, group order, partial-tail behavior, and intermediate use counts agree. Every vector, mask, and group-phase result of the parallelize has exactly its corresponding serialize operand use and no side use. The reverse composition is not an identity: serialization may discard inactive lanes before a later parallelizer compacts values across the original group boundary.
  • ElementwiseCardinalityCommute moves one exact elementwise Compute actor across already existing parallelize or serialize boundaries without selecting a vector factor, tail policy, logical domain, or graph boundary. The initial catalog admits a Compute with at least one operand and exactly one result. Registered scalar and vector forms must be lane-wise identical; attributes, phase, masks, and every operand correspondence must agree; and the operation must have no state, memory effect, cross-lane behavior, or blocking side use.

Let P(x, phase) denote one parallelize and S(v, mask, group_phase) one serialize. Subscript i follows Compute operand ordinal. The only four source-to-result shells are:

Direction and source shell Deterministic result shell
MoveInside: compute_scalar(x_i) -> P(result, phase) P_i(x_i, phase) -> compute_vector(v_i)
MoveInside: S_i(v_i, mask, group_phase) -> compute_scalar(x_i) compute_vector(v_i) -> S(result, mask, group_phase)
MoveOutside: P_i(x_i, phase) -> compute_vector(v_i) compute_scalar(x_i) -> P(result, phase)
MoveOutside: compute_vector(v_i) -> S(result, mask, group_phase) S_i(v_i, mask, group_phase) -> compute_scalar(x_i)

The decision's adapter set is exactly the source shell's one result-side adapter or all operand-side adapters. Operand-side adapters have identical width and sideband inputs. The adapter at Compute operand ordinal zero is the canonical sideband representative: only its mask and group-phase results for P_i, or scalar-phase result for S_i, may escape an operand-side shell; the corresponding results of every other operand-side adapter have no side use. A result-side adapter's sideband results form the source shell boundary. Result construction creates operand-side adapters in Compute operand order, uses ordinal zero as the result boundary representative, or creates the one result-side adapter. It replaces every source boundary data and sideband use with that exact result boundary and removes the complete source shell. Every source adapter data result has only its matching Compute operand use, or the Compute result has only the one result-side adapter data use. No other shell result may have a side use. An inactive partial lane may be evaluated only when that evaluation is proven unobservable and safe to speculate; otherwise the representative mask must be proven all-active. * PureComputeFanoutRefactor converts between one single-result Compute actor followed by broadcast and coupled-input broadcasts followed by replicas. The Compute actor must be deterministic, total, stateless, regionless, and effect-free. Replication preserves one common correspondence across every operand and requires at least two result uses. It creates one replica per member of the result's complete canonical sink set, ordered by the existing CanonicalGraphConsumerEndpointRef wire. Factoring requires exact equality of operation schema, attributes, types, ordered input sequences, and activation correspondence for all replicas in the decision's complete set. It uses the lowest canonical replica ActorRef as the construction source, redirects every named replica result use to its one factored result, and removes the other named replicas. Replication consumes the complete source fanout and factoring consumes exactly the named replica group; no unlisted branch or replica is silently rewritten. A generic purity trait alone is insufficient. * ActivationPreservingConstantFold replaces one foldable single-result Compute actor and its otherwise unused constant operands with one exact typed dataflow.constant only when all constants use the same control activation and the registered operation folds exactly. It cannot create a timeless constant, merge distinct activation streams, or erase a constant that has another consumer. * GraphDefinitionRefactor::Split clones one graph definition for the named nonempty proper partition of its static launches and retargets only those launches. The graph definition is private and every launch is a static, module-local site inside a private thread definition under the baseline graph ABI; the complement is derived from the parent's complete static call set. Merge accepts only graph definitions with equal signatures, protocols, memory and stream semantics, and complete alpha-isomorphic actor graphs. Split assigns the normalized selected side to the clone. Merge uses the lower canonical GraphRef as its source body and retargets launches of the higher reference. Both preserve every launch identity, launch-owned source map, and launch-local binding. Dynamic invocations are never enumerated or individually cloned. * ElementwiseVectorDecompose has exactly the operation, shape, mask, poison, activation, and construction contract defined by its linked owner. It decomposes an already selected semantic vector actor; it does not revisit Structured vectorization, memory atomicity, reduction order, or Mapping.

372

The catalog excludes unrestricted graph CSE or DCE, a greedy canonicalizer, generic selector factoring, stateful or stream fusion, memory forwarding or coalescing, associative arithmetic reassociation, and hidden route, buffer, tag, grouping, or representation changes. A future rule requires a new catalog version and its own typed pattern, complete legality proof procedure, normalized parameters, deterministic result construction, and focused semantic anchors. Neither a plugin registry nor a generic rewrite-property DSL is part of this contract.

381

Mandatory Canonical Dataflow finalization remains non-branching and separate. It canonicalizes and verifies the private result but cannot choose a rewrite, merge graph definitions, select synchronization topology, or prove arbitrary parent-child equivalence. Each rule's legality procedure is the sole authority for that edge. Differential simulation or formal tools may validate an implementation, but finite observations do not replace the rule contract.

388

2. Execution Ownership Model

390

Loom's execution target is a heterogeneous system containing HostCore execution and one or more AccCore execution resources. Fabric owns each typed AccCore occurrence, its node-local InstructionCore description, its SpatialCore occurrence, and the typed attachment to an exact fabric.module template. fabric.module remains the SpatialCore or CGRA template only; it does not own the physical AccCore occurrence or InstructionCore description. The typed system ownership contract is specified by docs/spec-fabric-system-adg.md.

399

The front-end IR in this document remains a software and logical execution model. SystemMapping binds logical execution cells to physical AccCore instances. TechMapping and SpatialMapping realize canonical graph execution on the selected SpatialCore resources.

404

The front-end IR separates these execution roles:

406
Execution role Front-end IR carrier
HostCore Host-call-context callable body code outside any dataflow.thread.launch; imported LLVM callables remain llvm.func
Logical execution domain A dataflow.thread definition (Symbol-bearing, module-scope) plus each caller-side dataflow.thread.launch. A dense instance is identified by its coordinate tuple; a dynamic-work instance is identified by its WorkItemId. SystemMapping binds either logical identity to an AccCore.
InstructionCore The body of a dataflow.thread definition, minus its dataflow.graph.launch ops, plus InstructionCore-legal llvm.call or func.call callees after inlining or specialization. The body is "what one logical execution cell runs once binding maps it to a physical AccCore".
SpatialCore Each dataflow.graph definition referenced by a dataflow.graph.launch inside a dataflow.thread definition's body, again per bound logical execution cell.
413

A single dataflow.thread.launch starts exactly one domain instance of the kind declared by the referenced dataflow.thread: either a multidimensional, zero-based dense domain or a responsibility-tracked dynamic work domain. The thread body is "what one logical execution point runs"; dense coordinates or the current work-item identity distinguish executions. The domain is a software concept and does not commit to a specific fabric topology. A fabric whose physical PE / memory graph is not a Cartesian mesh is supported by the same Mapping profiles. The binding from a logical execution point to a physical AccCore is a SystemMapping concern; see docs/spec-mapping-artifact.md and docs/spec-pnr.md for SystemMapping execution binding and PnR.

425

Every dataflow.thread body may contain InstructionCore code and dataflow.graph.launch ops, but it cannot launch another thread. A dynamic worker may use dataflow.work.spawn to publish a child in its current domain; that operation does not create or target another thread launch. Dynamic instances become physical AccCore execution slots only through SystemMapping.

432

An InstructionCore-only thread body is legal. Failure to form a canonical graph must not synthesize a new accelerator boundary or move unselected host code into a thread.

436

Thread completion and graph/dataflow control are distinct token domains. !dataflow.thread_token is the inter-thread asynchronous completion token produced by dataflow.thread.launch. none values are the graph-control, graph-completion, streaming-control, and memory-order tokens used inside dataflow. There is no implicit cast or general conversion between the two domains. dataflow.thread.wait consumes one or more !dataflow.thread_token values for caller-side causal synchronization and emits no SSA value or graph-control value. It is not a memory barrier.

446

Thread hierarchy transforms before SystemMapping are legal only as explicit optimization policies. They may reorder independent thread levels, collapse adjacent independent levels, or tile and split a level when the transform preserves the logical instance set, each instance's scalar values, memory-order constraints, and thread-completion causal order. Launch placement remains caller-side only. The deterministic baseline policy performs only annotation and canonicalization; it must not silently change hierarchy shape as a verifier or parsing side effect.

455

2.1 IR Carrier Responsibilities

457
  • llvm.func remains the sole function and ABI owner for a callable imported from the final linked LLVM module. func.func is used only for a genuinely standard-MLIR-native callable or helper; it cannot mirror the LLVM ABI. Neither callable kind chooses HostCore or AccCore ownership. Call-context classification decides where calls are legal.
462
  • loom.spatial_region is temporary compiler IR inside a dataflow.thread. It owns one structured graph candidate with normalized value, stream-channel, and memory boundary segments. It never appears in a finalized Canonical Dataflow Program.MS1V21 · MS1V linked-input-179 · linked input 179generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:cb03cffe9a6fce2f72e3c64f with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V620 · MS0V6 linked-input-179 · linked input 179generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:037b2351ec38125b2cd5ae56 with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V319 · MS0V3 linked-input-179 · linked input 179generator constraint — feeds the generated inputsMLIR passage 4: Graph bodies exclude calls and nested callables1000 generated · 824 paired · 176 rejectedopen PBT →MS0V218 · MS0V2 linked-input-179 · linked input 179generator constraint — feeds the generated inputsMLIR passage 2: Graph launch execution boundary5000 generated · 5000 pairedopen PBT →
466
  • dataflow.thread is the logical accelerator execution-domain definition (Symbol-bearing, module-scope, function-like). It owns the kernel body and one domain kind. A dense definition owns coordinate rank through its canonical entry-block shape; a dynamic definition designates one ordinary argument as its work-item payload. It does not itself execute; dynamic logical instances are materialized by one or more dataflow.thread.launch ops at use sites, then SystemMapping decides which instances occupy physical AccCore slots.
  • dataflow.thread.launch is the logical accelerator execution boundary. It references a dataflow.thread callable by symbol, supplies async dependencies and ordinary body operands, plus one non-negative extent per dense coordinate dimension. A dynamic launch instead supplies one root work item and no extents. Both produce one collective completion token.
  • dataflow.work.spawn publishes one child of the currently executing dynamic work item after atomically acquiring its termination responsibility. It is illegal in a dense thread or any graph and is not nested thread launch.
483
  • dataflow.graph is the SpatialCore leaf DFG definition (Symbol-bearing, module-scope, function-like). Its body cannot contain callable definitions, llvm.call, func.call, dataflow.thread.launch, dataflow.graph.launch, or another dataflow.graph definition.MS0V3n/a22 · MS0V3 selected-output · selected outputproperty — checked on every compiled pairMLIR passage 4: Graph bodies exclude calls and nested callables1000 generated · 824 paired · 176 rejectedtrace evidence 824 samples / 57 classesopen PBT →
488

It is final target-independent software IR: its validity does not assert that any current Fabric can realize it. TechMapping owns that decision.

490
  • dataflow.graph.launch is the SpatialCore execution boundary inside a dataflow.thread definition's body. It references a dataflow.graph callable by symbol, supplies dependency events, value inputs, stream channel bindings, and memory imports, and yields value outputs, memory exports, and a trailing done : none result.MS0V299.5%23 · MS0V2 selected-output · selected outputproperty — checked on every compiled pairMLIR passage 2: Graph launch execution boundary5000 generated · 5000 pairedtrace evidence 1000 samples / 14 classesopen PBT →
496

Function definitions remain module-level symbols in this design. dataflow.thread definitions are also module-level symbols (and not symbol tables themselves) and do not physically contain

499

llvm.func or func.func definitions. An llvm.call or func.call inside a dataflow.thread definition's body is an InstructionCore call. If the callee contains code that must become a dataflow.graph definition, Part 3 must inline or specialize that callee into the active thread definition before graph extraction. A dataflow.thread.launch is invalid transitively inside every thread or graph definition. Non-inlined InstructionCore calls may remain only when their callee body is graph-free after this preparation.MS1V27 · MS1V linked-input-140 · linked input 140generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:cb03cffe9a6fce2f72e3c64f with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V626 · MS0V6 linked-input-140 · linked input 140generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:037b2351ec38125b2cd5ae56 with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V325 · MS0V3 linked-input-140 · linked input 140generator constraint — feeds the generated inputsMLIR passage 4: Graph bodies exclude calls and nested callables1000 generated · 824 paired · 176 rejectedopen PBT →MS0V224 · MS0V2 linked-input-140 · linked input 140generator constraint — feeds the generated inputsMLIR passage 2: Graph launch execution boundary5000 generated · 5000 pairedopen PBT →

508

3. Constitutional Rules

510

The eight rules below are invariants that downstream passes and verifiers must enforce; the rest of this spec is a refinement of how each rule lands in IR.

514
  1. dataflow.thread is the logical execution-domain primitive used for selected accelerator work. It is a Symbol-bearing, module-scope, function-like definition (Part 3 Section 5.4.1); logical instances are materialized by dataflow.thread.launch and, for one dynamic domain only, its controlled dataflow.work.spawn operations. Launches appear only in host/runtime orchestration outside every thread or graph definition. An instance becomes a physical AccCore execution slot only through SystemMapping. The thread definition's body has a thread_ctrl : none block argument that fires once the logical thread instance starts executing (dense entry-block layout: (args_*, thread_ctrl, coord_*); dynamic work has no coordinate suffix, see Section 5.4.1). The body may contain InstructionCore operations and InstructionCore-legal llvm.call or func.call operations, but not callable definitions or dataflow.thread.launch ops.
  2. dataflow.graph is a leaf-level definition. It is also a Symbol- bearing, module-scope, function-like definition (Part 3 Section 5.5); execution is materialized by dataflow.graph.launch ops inside a thread definition's body. Its body must not contain any llvm.func, func.func, llvm.call, func.call, dataflow.thread.launch, dataflow.graph.launch, or another dataflow.graph definition. The graph body is a single graph-kind region; it already permits feedback edges (accepted semantics). A thread body may contain InstructionCore code and dataflow.graph.launch ops, but it never launches another thread. The verifier enforces this launch containment transitively (see Section 9).
  3. Every dataflow.graph definition has an explicit %start : none entry value and a structural dataflow.graph.return with four segments: values, streams, memories, and complete. complete is a mandatory non-empty variadic unordered all-of set of none values. A no-work graph may return %start as its sole completion witness; real work, including zero-output work, must expose a causally derived frontier. The launch-facing result is exactly done_out = all_of(graph.return.complete). It never appears among the return operands, and no effect scan or graph-quiescence rule can replace the explicit frontier.
  4. The HostCore-to-AccCore data plane is the explicit ordinary-operand segment of dataflow.thread.launch. Values, memrefs, dynamic source lower bounds, source steps, and other launch parameters cross directly as typed SSA operands and matching thread block arguments. Dedicated map_info, partition-domain, or layout carrier ops are not part of this ABI.
  5. Graph-local memory ordering is constructed in the front-end by one recursive graph-region owner. It discovers basic root alias partitions, threads independent write and read frontiers through sequential and structured control, and emits ordinary Dataflow event edges. Unknown accesses conservatively cover every known partition. There is no persistent alias oracle, dependence snapshot, or later wiring pass. The complete transfer rules are specified in docs/spec-compiler-part-3-mem.md.
564
  1. loom.spatial_region is the temporary publication boundary inside an existing dataflow.thread. Its operands are normalized as value inputs, stream input channels, memory inputs, and stream output channels; its results are value outputs followed by memory outputs. Each stream input has one affine source_map from the consumer thread domain to the producer thread domain.MS1V31 · MS1V linked-input-150 · linked input 150generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:cb03cffe9a6fce2f72e3c64f with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V630 · MS0V6 linked-input-150 · linked input 150generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:037b2351ec38125b2cd5ae56 with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V329 · MS0V3 linked-input-150 · linked input 150generator constraint — feeds the generated inputsMLIR passage 4: Graph bodies exclude calls and nested callables1000 generated · 824 paired · 176 rejectedopen PBT →MS0V228 · MS0V2 linked-input-150 · linked input 150generator constraint — feeds the generated inputsMLIR passage 2: Graph launch execution boundary5000 generated · 5000 pairedopen PBT → The lowering collects all explicit candidates before
570

attempting publication, performs conversion and native validation on a scratch module, and replaces the live module only on success. A public pass failure therefore leaves temporary candidates and never exposes a partial

573

canonical graph. Current publication supports nested scf.if completion propagation.MS1V35 · MS1V linked-input-90 · linked input 90generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:cb03cffe9a6fce2f72e3c64f with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V634 · MS0V6 linked-input-90 · linked input 90generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:037b2351ec38125b2cd5ae56 with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V333 · MS0V3 linked-input-90 · linked input 90generator constraint — feeds the generated inputsMLIR passage 4: Graph bodies exclude calls and nested callables1000 generated · 824 paired · 176 rejectedopen PBT →MS0V232 · MS0V2 linked-input-90 · linked input 90generator constraint — feeds the generated inputsMLIR passage 2: Graph launch execution boundary5000 generated · 5000 pairedopen PBT → Stream channel segments become payload-typed graph stream

575

ports plus launch-site channel bindings; receive/send sites rendezvous with the recursively lowered execution frontier and are removed. Input source_map attributes are preserved exactly, and channel handles never

578

enter the canonical graph body. One binding denotes one ordered dynamic event sequence. A fixed structured scope may contain multiple sequential or structured mutually exclusive sites. Lowering emits one fixed ordinal schedule, filters inactive branch sites, demuxes each input from the filtered ordinal, and muxes outputs back into that same dynamic order.MS1V39 · MS1V linked-input-36 · linked input 36generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:cb03cffe9a6fce2f72e3c64f with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V638 · MS0V6 linked-input-36 · linked input 36generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:037b2351ec38125b2cd5ae56 with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V337 · MS0V3 linked-input-36 · linked input 36generator constraint — feeds the generated inputsMLIR passage 4: Graph bodies exclude calls and nested callables1000 generated · 824 paired · 176 rejectedopen PBT →MS0V236 · MS0V2 linked-input-36 · linked input 36generator constraint — feeds the generated inputsMLIR passage 2: Graph launch execution boundary5000 generated · 5000 pairedopen PBT →

583

A schedule with more than one static endpoint is materialized as a balanced tree of two-lane selective routers. Each internal node derives its in-range branch selector from the same proved endpoint ordinal and routes both the payload and the remaining local ordinal only into the selected subtree. Output collection uses the corresponding selector phase. Publication never creates one mux or demux whose lane count grows with the number of static endpoint sites; this is a mechanical projection of the fixed schedule, not a Fabric-dependent fan limit or a Dataflow rewrite decision.

591

Branches may have unequal or empty site sets, and a later branch selector may depend on an earlier input event. Enclosing loops repeatedly activate the same schedule, so one or several static body sites may each fire dynamically. Across repeated thread launches, each endpoint binding and logical point concatenates these per-instance sequences in deterministic launch issue order. Channel delivery pairs the resulting producer and consumer sequences by message ordinal after applying source_map; it doesMS0V240 · MS0V2 linked-input-55 · linked input 55generator constraint — feeds the generated inputsMLIR passage 2: Graph launch execution boundary5000 generated · 5000 pairedopen PBT →MS0V341 · MS0V3 linked-input-55 · linked input 55generator constraint — feeds the generated inputsMLIR passage 4: Graph bodies exclude calls and nested callables1000 generated · 824 paired · 176 rejectedopen PBT →MS0V642 · MS0V6 linked-input-55 · linked input 55generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:037b2351ec38125b2cd5ae56 with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS1V43 · MS1V linked-input-55 · linked input 55generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:cb03cffe9a6fce2f72e3c64f with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →

598

not pair thread activations or create activation-owned segments. EndpointMS0V240 · MS0V2 linked-input-55 · linked input 55generator constraint — feeds the generated inputsMLIR passage 2: Graph launch execution boundary5000 generated · 5000 pairedopen PBT →MS0V244 · MS0V2 linked-input-180 · linked input 180generator constraint — feeds the generated inputsMLIR passage 2: Graph launch execution boundary5000 generated · 5000 pairedopen PBT →MS0V341 · MS0V3 linked-input-55 · linked input 55generator constraint — feeds the generated inputsMLIR passage 4: Graph bodies exclude calls and nested callables1000 generated · 824 paired · 176 rejectedopen PBT →MS0V345 · MS0V3 linked-input-180 · linked input 180generator constraint — feeds the generated inputsMLIR passage 4: Graph bodies exclude calls and nested callables1000 generated · 824 paired · 176 rejectedopen PBT →MS0V642 · MS0V6 linked-input-55 · linked input 55generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:037b2351ec38125b2cd5ae56 with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V646 · MS0V6 linked-input-180 · linked input 180generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:037b2351ec38125b2cd5ae56 with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS1V43 · MS1V linked-input-55 · linked input 55generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:cb03cffe9a6fce2f72e3c64f with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS1V47 · MS1V linked-input-180 · linked input 180generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:cb03cffe9a6fce2f72e3c64f with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →

599

sites nested under scf.parallel or scf.forall have no inferred traversal order and fail before publication. Unselected or non-fixed graph-owned parallel forms also fail closed.MS0V244 · MS0V2 linked-input-180 · linked input 180generator constraint — feeds the generated inputsMLIR passage 2: Graph launch execution boundary5000 generated · 5000 pairedopen PBT →MS0V345 · MS0V3 linked-input-180 · linked input 180generator constraint — feeds the generated inputsMLIR passage 4: Graph bodies exclude calls and nested callables1000 generated · 824 paired · 176 rejectedopen PBT →MS0V646 · MS0V6 linked-input-180 · linked input 180generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:037b2351ec38125b2cd5ae56 with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS1V47 · MS1V linked-input-180 · linked input 180generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:cb03cffe9a6fce2f72e3c64f with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →

602
  1. dataflow.thread and dataflow.graph definitions are both IsolatedFromAbove. No operation inside either definition's body may directly use an SSA value defined in the surrounding scope. Every boundary value must appear as an explicit launch op operand and as a matching entry block argument of the referenced definition. For a dataflow.thread.launch, the operand list is the HostCore-to-AccCore launch ABI: every value crosses as an ordinary typed operand. For a dataflow.graph.launch, operands and results are the explicit SpatialCore data/control ports. A dataflow.thread.launch completion token expresses launch-retirement causality only. The dataflow.graph.launch op resolves its callee through SymbolUserOpInterface; it does not implement MemoryEffectsOpInterface or project sibling-callee effects. Each definition carries RecursiveMemoryEffects so walkers of that callable can observe its body effects. Launch-site ordering and retirement are represented by explicit dependencies, the segmented memory-capability ABI, the done result, and native finalized-program validation.
  2. Effect visibility contract. Every front-end op whose execution affects memory state must declare its effects through MLIR's MemoryEffectOpInterface (or an equivalent recursive trait) accurately enough that generic optimizers preserve the intended observable behavior. Causal ordering and completion are defined by their individual op contracts, not by memory effects. The baseline policy uses MLIR's default-resource barrier pattern -- broad, conservative MemRead + MemWrite declarations -- where a precise per-resource binding would require op-side machinery outside this contract. Tighter per-resource bindings (for example, load/store keyed on the $mem operand) are explicit extensions.
634

4. Glossary

636
  • HostCore. The general-purpose CPU that runs host-call-context callable code outside any dataflow.thread.launch. Imported LLVM code remains in its llvm.func envelope.
  • AccCore. One typed physical accelerator execution resource owned by fabric.system, containing a node-local InstructionCore description and a SpatialCore occurrence attached to an exact fabric.module template. Part 3 does not create physical AccCore occurrences; it creates logical accelerator work that SystemMapping later binds to AccCore resources.
  • InstructionCore-callable function. A module-level llvm.func or native func.func that Part 2 classified as legal to call from code running inside a dataflow.thread definition's body. Such a function remains a symbol; Part 3 either preserves calls to it as InstructionCore calls or inlines or specializes it before graph extraction.
  • Logical thread coordinate. One zero-based index value in the trailing coordinate segment of a dataflow.thread entry block. A rank-K launch provides a coordinate tuple in the Cartesian domain [0, extent_0) x ... x [0, extent_K). Coordinates carry no physical topology or execution-order promise.
  • Logical thread domain. The instance set derived from one launch and its callee's closed domain kind. A dense domain uses the extent vector; a DynamicWork domain starts with one root and grows through registered child work. SystemMapping, rather than a software mapping attribute, decides how either kind shares or occupies AccCore resources.
  • Derived data view. An ordinary typed value or memref view computed from launch operands and logical coordinates. Source-IV reconstruction, tiling, local ranges, and explicit linearization remain ordinary candidate semantics rather than a second partition-domain ABI.
  • Thread token. A value of type !dataflow.thread_token, a one-shot completion signal modelled on !async.token. It belongs to the inter-thread asynchronous-completion domain, not to the none-typed graph/control token domain.
  • Thread control token. A none-typed entry-block argument of a dataflow.thread definition's body (named thread_ctrl, positioned after the function-signature args per Section 5.4.1). It is the per-instance AccCore start signal used to launch root dataflow.graph.launch ops.
  • Basic alias partition. A graph-local compiler analysis bucket keyed by a recognized memory root. View-like values are peeled to their root; graph boundary arguments are conservatively grouped unless explicit no-alias evidence distinguishes them; fresh allocations have distinct roots; globals and raw pointer bases must be imported explicitly before a graph is finalized. Partition identity is not written into IR.
  • Memory dependence edge. An ordinary none SSA causal edge emitted by the recursive graph owner from the current per-partition frontier. No persistent dependence snapshot is retained.
  • Loop-carried memory state. The canonical (write_frontier, read_frontier) pair carried recursively for one alias partition. Touched components are materialized with independent dataflow.carry and false/true dataflow.demux projections. Specified in docs/spec-compiler-part-3-mem.md.
  • Phase bit. A loop-control bit produced by dataflow.stream for counted loops: it fires true once per body iteration and one trailing false token that closes the activation. The combined (true, ..., true, false) stream phases structural state and may select the body and exit projections of loop-carried memory state, but is not itself a memory frontier. The exact timing semantics live in docs/spec-dataflow-part-1-streaming.md.
  • Streaming token. Any typed token stream consumed or produced by the streaming primitives dataflow.stream, dataflow.gate, dataflow.invariant, and dataflow.carry. Payloads may be ordinary data, i1 phase, or none control according to the operation contract. Streaming tokens carry phase, iteration, or payload information rather than memory-frontier authority; their precise timing semantics are owned by docs/spec-dataflow-part-1-streaming.md. The phase bit above is one specific streaming token.
  • Memory-order token. A none-typed token used to encode one component or join of alias-aware ordering between memory accesses inside a dataflow.graph definition's body. Each per-partition state pair (see Canonical Frontier State in docs/spec-compiler-part-3-mem.md) flows through its own memory-order tokens; the One Recursive Owner transfer in that document combines a structural permission token with a memory-order predecessor token at each load / store. Memory-order tokens do not encode dynamic execution path (that is the structural execution role of Section 2.1 there).
  • Aggregation-form forall. An scf.forall with shared_outs, op results, or non-empty scf.forall.in_parallel combining actions such as tensor.parallel_insert_slice.
  • Effect-form forall. An scf.forall with no shared_outs, no op results, and an empty scf.forall.in_parallel terminator. Its observable behavior is expressed through explicit memory effects.
719

5. IR Additions

721

This section enumerates every new dialect element the front-end introduces. All additions are local to the dataflow and loom namespaces; nothing outside this list is added.

725

5.1 New Types

727
  • !dataflow.thread_token
  • One-shot completion signal. Equivalent of !async.token for the Loom front-end.
  • Belongs only to the inter-thread asynchronous-completion domain. It is not a none-typed graph-control token, and there is no implicit cast between the two domains.
  • Runtime ABI ownership and refcounting are specified by the runtime ABI; Part 3 manipulates the type as an SSA value.
736

This spec introduces no other types. Host-to-AccCore values cross the launch boundary as ordinary typed SSA operands; no wrapper or provenance-only type is introduced.

740

5.2 Attributes And Interface Instances

742

No Loom-specific thread mapping attribute is introduced. Coordinate rank and domain come from the definition body shape and launch extents. Parallel versus temporal AccCore use is a SystemMapping relation plus event-relative ResourceUse, not a property copied into the program ABI.

747

Canonical Actor Schema Projection

749

Every operation admitted as a Canonical Dataflow actor resolves to exactly one registered OperationSchemaId. The operation's native definition remains the owner of its source semantics. A typed CanonicalDataflowActorOpInterface, implemented directly or through an external model, projects only the Loom contract needed downstream:

755
CanonicalActorSchemaProjection {
  operation_schema_id
  actor_kind
  closed_semantic_attribute_projection
  instance_verifier
  transition_descriptor_identity
}
765

The registry normally selects the complete native operation class. A native operation that is intentionally a generic semantic carrier may instead own a finite set of disjoint typed instance selectors. llvm.call_intrinsic uses this mechanism for irreducible LLVM intrinsics: LLVM's intrinsic ID and signature registry select the schema, and the reconstructed canonical overloaded spelling must equal the stored spelling byte for byte. Fast-math, operand bundles, argument or result attributes, unknown intrinsic IDs, invalid signatures, and noncanonical overload spellings fail closed unless the selected schema explicitly owns that state. A generic carrier name alone is never an actor identity, and no consumer may maintain an intrinsic-name table.

777

This notation describes one registered typed projection, not an IR operation, attribute, Artifact, or second semantic language. The projection must classify every property and attribute that can affect one firing. Unknown or unclassified actor state is rejected; consumers never copy an arbitrary attribute dictionary. A field may be excluded only when its owning spec proves it nonsemantic, as for source provenance.

784

For every scalar operation in a registered ScalarMath* family, the closed semantic attribute projection contains exactly one owner-coded SpecialMathAccuracyTier. A selected-Spatial operation missing that field, a non-special operation carrying it, or a projection that reconstructs it from afn, Fabric capability, or provider choice is invalid. afn remains the native source permission for approximate functions; the selected tier is the narrower canonical actor contract.

792

For memory actors the closed semantic projection contains the complete aggregate contract owned by docs/spec-dataflow-memory-consistency.md. Atomic load/store, atomic RMW, and compare-exchange projections include the exact source_alignment_bytes; dropping it while retaining ordering, scope, granularity, or volatility is an invalid partial projection. Geometry remains the separate nonpersistent CanonicalMemoryAccessView, but Fabric capability matching consumes both projections and may not reconstruct alignment from a type, endpoint width, or selected service.

801

Graph admission, canonical relation construction, Configured Function materialization, simulator dispatch, and Fabric capability matching all consume the same OperationSchemaId and projection. The canonical actor classifier is a derived query over this registry, not another whitelist. Simulator providers own executable transition implementations, while Hardware Sharing Groups own only genuine physical sharing relations. Neither may redefine software semantics or maintain a competing operation-name table.

809

The OperationSchema registry owns the stable versioned canonical codec and validator for OperationSchemaId. The Dataflow specifications that own closed projection atoms, including Canonical Service roles, memory access and mask forms, atomic ordering and RMW kind, vector atomic granularity, and SyncScopeRef, likewise own their stable wire tags and payload codecs. C++ enum values, TableGen case numbers, registration order, and printer spelling are not persistent encodings. A downstream artifact embeds the exact owner-produced bytes and fails import on an unknown or malformed value; it cannot define a local ordinal table.

819

Canonical actor values distinguish defined, poison, and undef state. A defined state carries the exact type-appropriate bits or logical identity; fixed vectors may carry state independently per lane. The owning operation schema defines propagation, masking, non-observation, freezing, and undefined behavior. In particular, selection does not observe its unselected value, inactive masked-memory lanes do not observe address or data, active stores may store poison and loads restore it, and graph outputs may carry poison or undef. There is no global rule that a terminal exceptional value is an execution error.

829

Pointer Values And Address Projections

831

Canonical Dataflow 3.0 distinguishes three semantic categories:

833
  • a memref graph memory port is a logical memory-object or memory-service capability;
  • index is a root-relative element coordinate whose selected representation width is owned by the Structured candidate; and
  • !llvm.ptr<AS> is a first-class dynamic pointer value whose representation and arithmetic semantics are owned by LLVM and the exact module DataLayout.
840

No category is an alias for another. In particular, a pointer is not a memory capability and is not an index value with a different spelling. Value and stream graph ports may carry scalar LLVM pointer values. Memory graph ports remain ranked or unranked memrefs. Pointer values may pass through ordinary token-plane actors and channels only when the selected Fabric transport admits their exact representation width.

847

For address space AS, the exact module-owned LLVM DataLayout derives:

849
PointerLayout(AS) = {
  representation_bits : P(AS)
  address_bits        : A(AS)
  kind                : StableIntegral | NonIntegral |
                        ExternalState | Unstable
}
858

P(AS) is the complete stored pointer representation. A(AS) is the width used by GEP address arithmetic. Neither is the canonical index width, the system physical-address width, nor a Fabric endpoint capacity. The same production DataLayout resolver is used by lowering, OperationSchema, Fabric admission, simulation, and Mapping verification. A missing, malformed, or inconsistent DataLayout is invalid. A non-integral, external-state, or unstable pointer layout without an exact provider is typed Unsupported; it is never silently projected to an integer.

867

LLVM pointer operations retain their source-owned operations and semantics. The initial registered pointer-arithmetic set contains llvm.getelementptr. OperationSchema owns only the stable typed projection required by downstream consumers: source element type, constant-versus-dynamic index pattern and constants, and no-wrap flags. The function type already owns the base, dynamic-index, and result types. llvm.ptrtoint, llvm.inttoptr, llvm.ptrtoaddr, pointer comparison, and address-space conversion remain source-valid LLVM operations but are not Canonical Dataflow 3.0 actors until each has an exact OperationSchema, Fabric admission rule, and execution provider. Replacing any of these operations with a new Dataflow pointer opcode would duplicate LLVM semantics.

879

GEP computes a DataLayout-derived byte offset from its complete typed index path. The final address formation adds that offset to the low A(AS) bits and preserves the high P(AS)-A(AS) representation bits. The exact address space, provenance, and inbounds/nusw/nuw poison contracts remain observable. Lowering may factor a GEP into ordinary integer offset arithmetic plus one final typed GEP only when it proves the same offset, poison, provenance, and intermediate-bound semantics. Otherwise the original typed operation remains one canonical actor.

888

Each Dataflow memory actor has one closed address projection:

890
MemoryAddressProjection =
    RootRelative {
      capability : memref
      element_index : index | fixed vector<index>
    }
  | PointerAddressed {
      service_capability : memref
      pointer : !llvm.ptr<AS> | fixed vector<!llvm.ptr<AS>>
    }
902

RootRelative computes the address from the capability base, element index, and exact element layout. PointerAddressed performs no extra base addition or element scaling: the pointer already denotes the byte address, while the capability selects the exact memory service, ordering domain, and provider. The service must resolve the pointer to one admitted object or region in the same address space. Encoding pointer access as base zero, byte element type, or integer index is forbidden. Loading or storing a pointer as ordinary data is legal only when the memory capability and physical data path admit all P(AS) bits and preserve the exact pointer kind.

912

An object-scoped memory service cannot be acquired from a statically exceptional pointer. Finalized-program validation resolves each service sourced from a thread formal through every exact root dataflow.thread.launch binding and rejects an undef or poison binding. Exceptional pointers remain legal value-plane data when no memory service is acquired from them. A structured candidate that hoists an unobserved exceptional branch placeholder into an unconditional service binding is non-finalizable; ownership selection must keep the controlling code outside that Spatial slice or choose a smaller dependency-closed region.

922

SCF-to-SCF optimization may choose rooted capability-plus-index addressing, first-class pointer execution in a SpatialCore, or InstructionCore ownership. That choice is an Evaluation/DSE result, not a type-system prohibition. Every pointer operation retained in a graph must have an exact OperationSchema, Fabric capability, provider, and simulator transition; otherwise that candidate fails closed.

929

Finalized Canonical Dataflow Programs use one derived identity attribute:

931
#dataflow.entity_id<42>
935

The payload is an unsigned 64-bit value; 42 above is illustrative. The Dataflow finalizer is the sole producer of this attribute. It appears as the namespaced dataflow.entity_id attribute on entity-bearing operations and inside the existing function-like argument dictionary for an imported logical memory root. It is never an authoring input, Mapping annotation, provenance handle, execution occurrence, or target binding. The canonical-labeling contract in Canonical Artifact Finalization And Entity Identity defines its closed carrier set and validation.

944

5.3 Thread Completion

946

No separate operation interface is introduced for thread completion. The launch, wait, and yield contracts are specified directly below.

949

5.4 New Operations (signatures only)

951

Each op below is given by its TableGen-level signature: arguments, results, regions, traits. Implementation bodies are out of scope for this spec.

955

The thread half of the front-end IR is split into a definition op (dataflow.thread, Section 5.4.1) and a launcher op (dataflow.thread.launch, Section 5.4.2). The definition op is a Symbol- bearing, function-like, module-scope callable; the launcher op references the definition by symbol and materializes one async launch instance per use site. Every executable thread in the IR is a def + at least one launch. This split mirrors gpu.func / gpu.launch_func.

964

5.4.1 dataflow.thread (definition)

966
arguments:
  TypeAttr:$function_type,
  SymbolNameAttr:$sym_name,
  StrAttr:$sym_visibility,
  Dataflow_ThreadDomainAttr:$domain,
  OptionalAttr<DictArrayAttr>:$arg_attrs;
results:
  none;
regions:
  SizedRegion<1>:$body;
traits:
  AutomaticAllocationScope,
  IsolatedFromAbove,
  Symbol,
  HasParent<"ModuleOp">,
  SingleBlockImplicitTerminator<"ThreadYieldOp">,
  DeclareOpInterfaceMethods<CallableOpInterface>,
  DeclareOpInterfaceMethods<FunctionOpInterface>,
  RecursiveMemoryEffects.
988
  • dataflow.thread is a Symbol-bearing, module-scope, function- like callable. It does not itself execute; one or more dataflow.thread.launch ops materialize launches of it.
991
  • function_type is a FunctionType whose inputs are the kernel's user-data operand types (T0, ..., TN) and whose results are empty. The thread definition has no SSA data results; the per-launch completion token is launch-side, not part of the callable signature. Asynchronous execution is expressed by launch dependencies and the mandatory launch completion token, not by the function type.MS1V363.2%48 · MS1V3 selected-output · selected outputproperty — checked on every compiled pairSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:d1dc2174ff936b5d9497d7eb with its governing context; report any stag5000 generated · 5000 pairedtrace evidence 1000 samples / 21 classesopen PBT →
998
  • sym_name is required and module-unique. sym_visibility is required and must equal "private" under the baseline visibility policy. The verifier rejects "public" and "nested" unless cross-module linkage is enabled by a separate spec.
  • domain is the closed DenseRectangular or DynamicWork { work_item_arg_ordinal } value owned by Part 4. A dynamic definition has coordinate rank zero and its ordinal must select exactly one function_type input. A dense definition has no work-item ordinal.
  • A dense entry block has the layout (args_*, thread_ctrl, coord_*); a dynamic entry block has (args_*, thread_ctrl):
1008
  • The first N block arguments mirror function_type.inputs exactly (each user body operand). Putting the signature args first preserves the upstream FunctionOpInterface invariant that the entry block's first N arguments correspond to function_type.inputs[0..N]. This matches the gpu.func precedent of "function args first, implicit extras after".MS1V891.1%49 · MS1V8 selected-output · selected outputproperty — checked on every compiled pairSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:20eb96f51c52af18e8b31078 with its governing context; report any stag5000 generated · 5000 pairedtrace evidence 1000 samples / 15 classesopen PBT →
1014
  • thread_ctrl : none is the per-launch AccCore start signal. It is produced by the launch op once async dependencies are satisfied and the AccCore instance begins execution. Root dataflow.graph.launch ops with no InstructionCore predecessor use this value as their ctrl_in operand.
  • For a dense definition, coord_0, ..., coord_{K-1} : index are the per-instance logical coordinates, one per launch-domain dimension, in source-dimension order. Their count is the definition's coordinate rank. Rank is derived from this canonical suffix after the function_type inputs and unique thread_ctrl; there is no duplicate rank, grid, or mapping attribute.
  • For a dynamic definition, the designated ordinary argument carries the current work-item payload. The runtime WorkItemId is execution identity, not an additional SSA argument or payload wrapper.
  • arg_attrs is indexed only by the args_* payload prefix. Forall promotion copies each captured source function argument dictionary, including arbitrary attributes such as llvm.noalias, into capture order. Locally defined captures have an empty dictionary. thread_ctrl and coord_* are not payload arguments and never inherit source argument metadata.
  • The body is IsolatedFromAbove. No SSA value defined outside the def's body may be used inside it; the launch's body operands are the only inputs.

5.4.2 dataflow.thread.launch

1039
arguments:
  Variadic<Dataflow_ThreadToken>:$asyncDependencies,
  Variadic<Index>:$extents,
  Variadic<AnyType>:$bodyOperands,
  FlatSymbolRefAttr:$callee;
results:
  Dataflow_ThreadToken:$asyncToken;
traits:
  AttrSizedOperandSegments,
  DeclareOpInterfaceMethods<SymbolUserOpInterface>.
1052

dataflow.thread.launch deliberately does not implement CallOpInterface. The op's only result is a !dataflow.thread_token, which is a launch-level async-completion handle, not a callable return value (the callee's function_type results are empty by Section 5.4.1). Generic call-graph and inliner consumers that read CallOpInterface::getResults() would get a misleading "call returns a thread token" picture; matching the upstream gpu.launch_func precedent (which also exposes async tokens but does not implement CallOpInterface), thread launch carries only SymbolUserOpInterface and resolves its callee through the explicit callee attribute.

1063
  • callee is a flat symbol reference that must resolve to a dataflow.thread definition in the same module. The verifier rejects launches whose callee cannot be resolved or whose resolved op is not a dataflow.thread.
  • bodyOperands types must equal callee.function_type.inputs position-by-position. Memrefs, values, source lower bounds, source steps, and other launch parameters all use this same ordinary operand segment.
  • extents contains exactly one index value per dense callee coordinate. Each extent must be non-negative. A dense rank-zero launch has no extent operands and creates one instance. If any dense extent is zero, the launch creates no instances and its collective token retires after its dependencies. Static verification rejects a provably negative extent; runtime admission rejects a negative value before creating any instance. A dynamic-work callee also has no extents, but creates one root item from its designated ordinary body operand; the callee's domain kind distinguishes it from dense rank zero.
  • Each dense instance receives a zero-based coordinate tuple. Source lower bounds and steps, when needed, are ordinary operands and the thread body reconstructs source_iv = lower + coordinate * step. The ABI specifies no row-major linearization, issue order, physical grid, or topology.
  • asyncDependencies is the variadic prefix of incoming !dataflow.thread_token dependencies. They form an all-of start ordering. The op always produces exactly one !dataflow.thread_token asyncToken result for collective retirement of all logical instances. For a dynamic-work launch this means the root source is closed and the active responsibility set owned by Part 4 is empty; queue emptiness alone is insufficient.
  • The op has no data results. Its mandatory token is the only launch-level completion result.

5.4.3 dataflow.thread.yield

1093
arguments:
  Variadic<NoneType>:$completionFrontier;
results:
  none;
regions:
  none;
traits:
  Terminator,
  ParentOneOf<["::dataflow::ThreadOp"]>.
1105
  • completionFrontier is a variadic unordered all-of frontier of none values. Structural verification checks only that each operand has type none and that the op terminates a dataflow.thread body. Finalized-program validation additionally requires the frontier to be a duplicate-free minimal terminal antichain for every dataflow.graph.launch in the thread body. Each launch's mandatory done : none must be yielded directly or lie in the path-aware causal closure of a yielded terminal event. Every executable SCF predecessor on which the launch exists must forward or cover its completion; a fallback is valid only on a mutually exclusive path where that branch-local launch does not exist. A dataflow.mux similarly covers a completion only on every selector lane where the completion may exist. Matching dataflow.demux activation can prove a launch absent from the other lanes.

Non-region causal edges come only from explicit completion semantics: dataflow.graph.launch done follows its dependencies, and dataflow.sync waits for all inputs. Other operations do not acquire completion semantics merely by accepting a none operand. Two distinct yielded events are invalid when either causally covers the other. Each remaining frontier member must also be necessary for at least one graph-launch completion that no other member covers. Independent launch events must all be covered, while a chain yields only its terminal event. A thread with no graph launch has an empty frontier. These checks derive from SSA and structured-control causality, never from textual operation order, and do not infer additional completion obligations from effects, DMA, or other operations. Any supported tensor-result aggregation has already been materialized by Part 2 as accepted explicit effects; an unmaterialized form is non-finalizable. The frontier therefore carries no thread data result.

In a DynamicWork thread, completion of the frontier retires the current work item exactly once. It does not directly retire the launch token; the token retires only after the domain responsibility set becomes empty.

1138

5.4.4 dataflow.work.spawn

1140
arguments:
  AnyType:$childItem;
results:
  none;
regions:
  none;
traits:
  DeclareOpInterfaceMethods<MemoryEffectOpInterface>.
1151
  • The op is legal transitively inside a dataflow.thread with a DynamicWork domain and outside every dataflow.graph. Its operand type must equal the definition's designated work-item argument type.
  • It atomically acquires one child responsibility before publishing that child to the current domain. The child receives the current item as parent and the next program-order child ordinal. The op has no target symbol, result handle, queue choice, priority, or Mapping field.
  • It is effectful and cannot be removed, duplicated, reordered across the current item's retirement, or treated as a nested dataflow.thread.launch. Exact identity and termination are owned by Part 4.
  • The enclosing DynamicWork definition cannot create, capture, send, receive, or bind a channel. Work-item publication does not synthesize channel message correspondence.
1165

5.4.5 dataflow.thread.wait

1167
arguments:
  Variadic<Dataflow_ThreadToken>:$asyncDependencies;
results:
  none;
traits:
  AtLeastNOperands<1>.
1176
  • A caller-side ordered stored-program wait. It consumes at least one thread completion token and completes only after every supplied token has retired. The operand set is unordered all-of.
  • The op produces no SSA result and no graph-control none value. It is not a memory barrier and does not define memory visibility.
  • The op is not Pure; it remains a causal wait in the stored program.
1183

5.4.6 Boundary Operands And Derived Views

1185

No dedicated boundary or partition-carrier operation is part of the canonical thread ABI. A launch operand and its matching ordinary thread block argument are the same typed software value. Alias, no-alias, bounds, and access-summary facts used by optimization remain ordinary MLIR analysis facts or semantic candidate operations; they are not encoded by a provenance-only passthrough op.

1192

Code inside the thread may derive source induction variables, subviews, local ranges, or an explicit linear id from ordinary launch operands and trailing logical coordinates. Those computations are program semantics and therefore survive whenever their results remain observable. Part 4 defines this boundary without introducing map_info, partition-domain, layout, or coordinate-query ops.

1199

5.5 Modifications to Existing Ops

1201

The graph half of the front-end IR is split into a definition op (dataflow.graph, Section 5.5.1) and a launcher op (dataflow.graph.launch). The definition op is a Symbol-bearing, function-like, module-scope callable; the launcher op references the definition by symbol from inside a dataflow.thread definition's body, supplies a per-launch ctrl_in : none operand and user data operands, and produces a per-launch done_out : none result and user data results. Every executable graph in the IR is a def + at least one launch.

1211

5.5.1 dataflow.graph (definition)

1213
arguments:
  TypeAttr:$function_type,
  SymbolNameAttr:$sym_name,
  StrAttr:$sym_visibility,
  OptionalAttr<DictArrayAttr>:$arg_attrs,
  OptionalAttr<DictArrayAttr>:$res_attrs;
results:
  none;
regions:
  SizedRegion<1>:$body;
traits:
  IsolatedFromAbove,
  Symbol,
  HasParent<"ModuleOp">,
  DeclareOpInterfaceMethods<CallableOpInterface>,
  DeclareOpInterfaceMethods<FunctionOpInterface>,
  RecursiveMemoryEffects.
1233
  • dataflow.graph is a Symbol-bearing, module-scope, function-like callable. It does not itself execute; one or more dataflow.graph.launch ops materialize launches of it.
  • The current function_type ABI is (T0, ..., TN) -> (R0, ..., RM) and contains only application payloads. input_segments and result_segments classify those payloads as values, streams, and memories. The graph %start and launch-facing done_out are explicit invocation protocol endpoints outside the function type. graph.return payload segments match all result types, and graph.return.complete derives done_out.
  • Every graph memory input and result is an established MLIR memref capability. An ordinary LLVM pointer may be a graph value or stream port, graph-body value, or graph value/stream result, but never a memory port. An addressed memory actor further requires the exact ranked, identity-layout, default-memory-space memref admitted by its operation schema and CanonicalMemoryAccessView.
  • sym_name is required and module-unique. sym_visibility is required and must equal "private" under the baseline visibility policy. The verifier rejects "public" and "nested" unless cross-module linkage is enabled by a separate spec.
  • The body is IsolatedFromAbove. All values used inside the graph definition's body must enter through the entry block.
  • The entry block has the layout (%ctrl_in : none, %arg_0 : T0, ..., %arg_N : TN). The application arguments match function_type.inputs; the distinguished leading ctrl_in block argument is the per-launch start signal and is not part of the function type. Accordingly, arg_attrs is indexed only by application arguments and has no entry for ctrl_in; res_attrs is indexed by application results. The custom assembly form preserves both arrays through textual and bytecode serialization.
  • The body's terminator is structural:

text dataflow.graph.return values(%final_values...) streams(%output_streams...) memories(%output_memories...) complete(%retirement_frontier...)

The payload segments, in that order, match all function results. complete contains one or more none witnesses and is the only completion truth. The compact %complete, %values... : none, types... form is permitted when there is one witness and the stream and memory segments are empty. * dataflow.graph lit tests use module-scope graph definitions with deterministic symbol names and dataflow.graph.launch use sites. Tests anchor the explicit start argument, segmented return payloads, non-empty completion frontier, and launch-facing done result. * C++ builders construct dataflow.graph as a function-like definition from (StringRef sym_name, FunctionType functionType, ArrayRef<NamedAttribute> attrs), with optional arg_attrs / res_attrs arrays carried in the function-interface attributes. The body is added via the standard FunctionOpInterface body-construction path, with the entry block carrying the leading none ctrl_in block argument and the user-data block arguments. * The op declares RecursiveMemoryEffects so module-scope walkers can observe per-callable effects. This does not provide an alternate launch-completion rule; retirement remains owned exclusively by graph.return.complete.

1293

5.5.2 dataflow.graph.launch

1295
Graph Launch Operation Contract
1297
arguments:
  FlatSymbolRefAttr:$callee,
  Variadic<NoneType>:$dependencies,
  Variadic<AnyType>:$valueInputs,
  Variadic<ChannelType>:$streamInputs,
  Variadic<AnyType>:$memoryInputs,
  Variadic<ChannelType>:$streamOutputs;
results:
  Variadic<AnyType>:$valueResults,
  Variadic<AnyType>:$memoryResults,
  none:$done;
traits:
  DeclareOpInterfaceMethods<SymbolUserOpInterface>.
1313
  • callee is a flat symbol reference that must resolve to a dataflow.graph definition in the same module. The verifier rejects launches whose callee cannot be resolved or whose resolved op is not a dataflow.graph.
  • The verifier checks each operand and result segment against the callee's normalized [value, stream, memory] FunctionType segments. Stream payloads bind to consumer or producer !dataflow.channel<T> endpoints; they are not launch SSA data results. The mandatory trailing done : none result is the per-launch retirement event.
1323
Graph Launch Memory Binding
1325

Value and stream bindings require their exact existing type relations. Memory input binding uses one closed relation, owned solely by this graph ABI:

1328
GraphLaunchMemoryBinding =
    Identity {
      actual: ranked or unranked memref<T>
      formal: the exact same memref<T>
    }
1336

An admitted memref view already resolves through its Dataflow-owned LogicalMemoryViewRef; graph launch neither derives another view nor accepts a pointer in the memory segment. A pointer needed by the graph is passed through an exact value or stream binding. Every memory type mismatch is invalid. Memory results exactly match the callee's memref result types. Pointer results are legal only in the value or stream result segment.

1343
Graph Launch Stream And Execution Contract
1345
  • Each stream input binding carries one symbol-free affine source_map. Its dimensions are the consumer thread coordinates and its results select the producer thread coordinates. Direction is derived from the launch operand segment: stream inputs are consumer bindings and stream outputs are producer bindings. There is no independent channel direction or mode attribute. Graph-launch verification owns local count and consumer-domain checks. The finalized-program validator owns the cross-launch relation: one producer, at least one consumer, producer/result-rank agreement, bounds over the full consumer domain, and complete permitted channel use topology. Both endpoint domains must be DenseRectangular; a DynamicWork domain is not interpreted as dense rank zero.
  • The op materializes a per-launch firing of the callee at this exact program point. done_out is the all-of of the callee's graph.return.complete operands. Their causal closure covers final values, stream close and boundary commit, memory capability establishment and promised visibility, all observable effects, invocation-local state close/reset, and non-detached async work. A graph with real work cannot use raw %start as a fake completion witness.
  • The op must appear inside a dataflow.thread definition's body, not at host scope and not inside another dataflow.graph definition's body. The verifier enforces this placement.
  • The launch intentionally does not implement MemoryEffectsOpInterface and does not project effects from its sibling callee. The native finalized-program validator proves that the callee's explicit complete frontier covers all outputs, state closure, and observable effects; explicit dependencies and memory capability ports carry launch-site ordering.
1372

5.5.3 dataflow.graph.wait

1374
arguments:
  Variadic<NoneType>:$completionFrontier;
results:
  none;
traits:
  AtLeastNOperands<1>.
1383
  • This op is the only explicit InstructionCore stored-program wait for graph retirement. It blocks until every event in its unordered all-of completion frontier has occurred and produces no SSA result.
  • The op must be transitively contained by exactly one dataflow.thread definition. It is invalid at host scope, inside dataflow.graph, or inside a nested thread definition.
  • Each operand is either a dataflow.graph.launch done result or an event whose path-aware causal closure contains at least one such result. The finalized-program validator proves that every operand is a valid terminal graph-completion frontier; textual order and generic none use do not establish that fact.
  • The wait inherits only the retirement and visibility obligations already owned by those graph completion events. It is not a system memory barrier, channel or NoC drain, thread-collective wait, or conversion to !dataflow.thread_token.
  • The op is not Pure. A lowering inserts it only before the first stored-program continuation that actually requires retirement. Deferred SSA value readiness, launch dependencies, channel transport, and dataflow.thread.yield remain their existing finer- or coarser-grained mechanisms and must not acquire redundant waits.
  • A channel message may cover a retirement-related causal dependency only when analysis proves one exact dynamic message relation under docs/spec-dataflow-part-1-streaming.md: the exact channel instance and endpoint bindings, the consumer-to-producer source_map, applicable path predicates, producer publication and consumer observation positions, and equality of the symbolic producer and consumer event positions. Coordinate equality, static-site equality, or identity source_map alone never proves that two repeated launch occurrences use the same message. Unknown cardinality or ordering fails closed and retains the required retirement dependency.
1413
  • loom.spatial_region is a transparent structured boundary. A blocking receive inside that region cannot be justified by a send that follows the region in the same stored-program strand merely because the published graph launch becomes asynchronous. Such a transformation would turn an inline-semantics deadlock into progress. A resulting retirement/send cycle therefore identifies a deadlocking or incorrectly cut candidate; lowering must not remove a wait by inventing a same-activation channel witness.MS1V53 · MS1V linked-input-204 · linked input 204generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:cb03cffe9a6fce2f72e3c64f with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V652 · MS0V6 linked-input-204 · linked input 204generator constraint — feeds the generated inputsSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:037b2351ec38125b2cd5ae56 with its governing context; report any stage at5000 generated · 5000 pairedopen PBT →MS0V351 · MS0V3 linked-input-204 · linked input 204generator constraint — feeds the generated inputsMLIR passage 4: Graph bodies exclude calls and nested callables1000 generated · 824 paired · 176 rejectedopen PBT →MS0V250 · MS0V2 linked-input-204 · linked input 204generator constraint — feeds the generated inputsMLIR passage 2: Graph launch execution boundary5000 generated · 5000 pairedopen PBT →
1421
  • dataflow.load and dataflow.store.
  • These dataflow primitives carry explicit memory-effect traits:
    • dataflow.load declares MemoryEffects<[MemRead]>.
    • dataflow.store declares MemoryEffects<[MemWrite]>.
  • These use MLIR's default memory resource. They are deliberately coarse in the baseline policy: any load may-read all memory, any store may-write all memory. This is sufficient for graph body effects to roll up through the graph definition's RecursiveMemoryEffects trait. It does not create launch-site effect projection.
  • Tightening these effects to a per-$mem-operand declaration (so two loads on disjoint memrefs become reorderable) is an explicit dataflow dialect extension.

  • No other dataflow op is modified by this spec.

1437

6. Per-scf Lowering Templates

1439

Graph-region lowering carries execution permission, captured values, and per-partition (write_frontier, read_frontier) state in one recursive traversal. The compiler-local transfer is:

1443
lower_region(E_in, values_in, {W_in[p], R_in[p]})
  -> (E_out, values_out, {W_out[p], R_out[p]})
1448

This transfer is not an IR object. The final graph contains only ordinary SSA values and the existing Dataflow primitives. Memref bindings remain static; only values, addresses, data, selectors, and event streams are projected. Leaf memory completion updates W/R but never silently replaces execution permission.

1454

This section records Dataflow templates for SCF boundaries. Recursive lowering applies the same transfer to scf.if, normalized scf.index_switch, source-sequential scf.for, scf.while, and fixed-domain effect-form scf.parallel / scf.forall. A zero-case scf.index_switch is replaced by its default region during structured normalization. Other unsupported source forms must be normalized by Part 2 before handoff of the selected Structured Program Candidate and are rejected if they remain in a graph.MS0V855 · MS0V8 linked-input-83 · linked input 83generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:d927678b3d8d4549df5bd40f with its governing context; report any stage attr5000 generated · 5000 pairedopen PBT →MS0V554 · MS0V5 linked-input-83 · linked input 83generator constraint — feeds the generated inputsSelected stage: loom-lower-scf-to-dfg. Test exactly output obligation mlir-obligation:f9b33846c2d82708b7bf1867 with its governing context; report any stage attr1000 generated · 964 paired · 36 rejectedopen PBT →

1463

The dataflow primitive set is (stream, carry, invariant, gate, mux, demux, sync, constant, load, store, yield). This section describes how SCF ops are mechanically rewritten with those primitives. The precise state machines and token lengths of stream, carry, invariant, and gate are the single source of truth in docs/spec-dataflow-part-1-streaming.md. The precise firing semantics of constant, sync, mux, and demux are the single source of truth in docs/spec-dataflow-part-2-control.md.

1473

The control op set is mux, demux, sync, constant. Crucially: the phase bit fed into carry / invariant / gate does not have to come from stream; any i1 SSA stream from arbitrary computation inside the graph plays the same role. This is what lets scf.while lower without a new op.

1479

Selection lanes follow the control-op contract. For i1 selectors, lane 0 is the false lane and lane 1 is the true lane:

1482
%false_value, %true_value = demux %cond, %value : (i1, T) -> (T, T)
%value = mux %cond, %false_value, %true_value : (i1, T, T) -> T
1487

For index selectors, lane k is operand/result position k. This convention is required for the templates below to be mechanical. dataflow.mux is selective: it consumes only the selector and selected input lane. dataflow.demux is selective: it emits only the selected output lane. dataflow.sync is the all-input rendezvous op. Control-only and mixed boundary-publication syncs are canonical software actors. A mixed boundary-publication sync has canonical shape (none, T) -> (none, T). TechMapping may realize a control-only sync with a wider all-control Fabric capability and must prove compatible arity and positional semantic widths for every selected realization. Lack of such a capability on one Fabric does not invalidate the canonical graph.

1499

Registered pure compute actors inside dataflow.graph, including the registered arithmetic, math, and LLVM computation operations, follow strict all-operand firing: each dynamic firing consumes one token from every operand and emits one token on every result. In particular, arith.select is an eager three-input compute op in this model, not a short-circuiting dataflow mux.

1506

SSA multi-use is token broadcast. If one SSA stream value has multiple uses, each use observes the same ordered token sequence. This is not a destructive single-consumer read. The scf.for template relies on this property because the loop phase stream independently drives carry, gate, and demux; those consumers do not need to fire in lockstep.

1513

Frontend memref<...> values are not stream values in this sense. They represent memory-region bindings for dataflow.load / dataflow.store. Lowering must not feed memref bindings through stream-shaping ops; it shapes address, data, operation, and explicit none memory-order streams instead. The generic result-selection templates below apply to scalar/data streams and none ordering streams. A memref-typed structured-control result inside graph extraction must be rewritten to explicit memory effects, kept in InstructionCore code, or rejected before graph lowering.

1523

Graph memory normalization must reject every residual LLVM load, store, memcpy, memset, atomic, volatile, or fence operation. They do not implement the canonical Dataflow memory capability and explicit completion-event contract; source order, a value result, or an effect scan cannot substitute for it. A supported LLVM memory operation must first normalize to dataflow.load, dataflow.store, or another explicitly specified canonical memory actor.

1530

The templates below show user-visible SSA value lowering. The same recursive owner threads independent none-typed write and read frontiers through each boundary as specified in docs/spec-compiler-part-3-mem.md; this is not an optional optimization or a later reconstruction pass.

1535

Def + Launch Output Convention

1537

The pseudocode templates in Section 6.1-Section 6.8 below show the graph body contents for clarity. Every template's actual lowering output is a dataflow.graph definition + a dataflow.graph.launch pair, with the body shown lifted to module scope and the launch carrying the per-instance ctrl/done plumbing:

1543
// At module scope (sibling of callable definitions):
dataflow.graph @<construction_local_sym>
    (%start : none, <user inputs>) -> (<user results>) {
  // <body contents per the template>
  dataflow.graph.return values(<user yield values>) streams() memories()
      complete(<retirement frontier>)
}

// At the explicit spatial-region site inside the enclosing
// dataflow.thread definition's body:
<user value results>, <memory results>, %done =
    dataflow.graph.launch @<construction_local_sym>
      deps(%dependency events) values(<value operands>)
      stream_inputs(<consumer channels>) memories(<memory imports>)
      stream_outputs(<producer channels>)
      : (<operand types>) -> (<value result types>, <memory result types>, none)
1562

The publisher creates a deterministic, collision-free construction-local symbol for each outlined graph. An existing loom.spatial_region.graph_name may be used only as a readability or debug stem. The temporary region does not own graph identity, and symbol spelling does not encode cut selection, source order, graph identity, or artifact identity.

1568

The templates therefore omit the def + launch wrap to keep the

1569

body's structural diff readable. The wrap is mandatory output, not an optimization, and is verified by the front-end's standard verifier rules in Section 9.MS1V88.1%56 · MS1V selected-output · selected outputproperty — checked on every compiled pairSelected stage: loom-lower-for-to-graph. Test exactly output obligation mlir-obligation:cb03cffe9a6fce2f72e3c64f with its governing context; report any stage at5000 generated · 5000 pairedtrace evidence 1000 samples / 6 classesopen PBT →

1573

Phase Phasing Rule

1575

A phase stream is loop control, not a plain valid bit. For a counted loop with N body executions, dataflow.stream emits N IV tokens and a phase stream T^N F. The final false token closes the activation and resets each stateful consumer, but it has no paired IV or body execution.

1580

The stream IV already has body cardinality and enters body arithmetic and memory directly. Parent-domain captured values from invariant have N + 1 tokens and are projected through dataflow.gate before body use. Recurrence values that also need a false-lane exit use selector-matched dataflow.demux; loop results and memory-frontier exits consume that false lane. A true body-local condition means the current body execution is not the last execution; a false body-local condition means it is the last execution.

1588

Different regions of one source loop may therefore have different phase streams. The loop-level phase decides whether the source loop continues or exits; a gated body phase controls state local to the body region whose value stream has already been normalized.

1593

6.1 scf.if

1595

Source shape:

1597
%r... = scf.if %cond -> (T_r, ...) {
  ... then computation using live-in streams ...
  scf.yield %then_r... : T_r, ...
} else {
  ... else computation using live-in streams ...
  scf.yield %else_r... : T_r, ...
}
1607

scf.if regions have no block arguments, but graph lowering must not let branch-local computation directly consume parent-phase data streams. For every non-memref stream live-in used by either branch, the lowering projects the stream into branch phase with the same selector that routes control. The %ctrl stream is supplied by the current lowering context: graph ctrl_in for a top-level if, loop body control for an if inside a loop body, or a selected parent-branch control stream for a nested if.

1616
# Lane convention: lane 0 = false, lane 1 = true
# demux %cond, %v : (i1, T) -> (T, T) yields (%v_else, %v_then)
# mux %cond, %v_else, %v_then : (i1, T, T) -> T (operand order:
# false-lane first, true-lane second)
%cond : i1
%t_else, %t_then = demux %cond, %ctrl : i1 -> (none, none)

# For every non-memref stream live-in %x : T used in either branch:
%x_else, %x_then = demux %cond, %x : (i1, T) -> (T, T)

# then-region runs with %t_then and %x_then...; produces %v_then...
# else-region runs with %t_else and %x_else...; produces %v_else...

%result = mux %cond, %v_else, %v_then : (i1, T, T) -> T
%done_after = mux %cond, %done_else, %done_then : (i1, none, none) -> none
1634
  • Each side's loads / stores fork from the side's local ctrl token and join back through a branch-local tail token.
  • Frontend memref<...> bindings are not demuxed. The branch-specific address, data, operation, and explicit none order streams are demuxed instead.
  • If a live-in is used by only one branch, the projection for the other branch is a dead output. Per the control-op contract, it is discarded by target lowering and does not require a dataflow.drop op or runtime queue.
  • Mutually exclusive branch tails are joined with mux, not sync. sync is only used inside one dynamically executed path, where all inputs are expected to fire. The un-selected branch produces no done token because demux only fires the selected output, while the exit mux waits only for the selected branch's done token.
  • If the scf.if has no else body, the false-path done is the false-path local ctrl token. If a branch has no memory side effect or other control-only work, that branch's done is its local ctrl token.
  • MLIR requires an else region whenever scf.if has results. An scf.if without an else region therefore has no results; only the control token needs to be joined.
  • Multi-result scf.if lowers one result mux per result position, all driven by the same %cond stream.
1658

For a three-token parent-phase invocation with %cond = [T, F, T] and a scalar live-in %x = [10, 20, 30]:

1661
Stream Tokens
%x_then [10, 30]
%x_else [20]
%v_then [then(10), then(30)]
%v_else [else(20)]
%result [then(10), else(20), then(30)]
%done_after [done_then0, done_else1, done_then2]
1670

Branch live-in demuxing is required for phase correctness. Without it, tokens for an unselected branch can remain buffered inside branch-local ops and be consumed by a later selected invocation at the wrong dynamic position.

1675

The capture owner may encounter an exceptional scalar placeholder introduced by CFG structuring at a graph boundary. It may project that lane to a defined zero wire value only when the corresponding graph entry argument has no SSA uses in the complete graph body, the lane is non-pointer and scalar, and the capture record retains an unusedByGraph provenance fact. This preserves the graph ABI while proving that the substituted bits cannot affect graph semantics. A value with any graph use, a pointer lane, or an unproven correspondence remains Undef/Poison and is rejected by the ordinary wire legality owner; no blanket exceptional-value replacement is permitted.

1685

If Boundary Translation

1687

This translation uses the recursive graph owner. The condition demuxes execution, captured non-memref values, and both frontier components for every partition touched by either branch. Each branch is lowered recursively. The same condition then muxes execution, each result position, W_P, and R_P componentwise.

1693

A missing else is an identity false lane. An unexecuted path forwards its incoming frontier and never performs a safe-address access or emits a fake completion. Same-path prerequisites use sync; mutually exclusive exits use mux. Execution remains distinct from both memory components.

1698

6.2 scf.while with scf.condition

1700

Source shape:

1702
%res... = scf.while (%a0_i = %init_i, ...) : (A_i, ...) -> (B_j, ...) {
^before(%a_i : A_i, ...):
  %cond, %b_j... = ... before computation ...
  scf.condition(%cond) %b_j... : B_j, ...
} do {
^after(%b_after_j : B_j, ...):
  %a_next_i... = ... after computation ...
  scf.yield %a_next_i... : A_i, ...
}
1714

The before-argument types A_i and the after/result types B_j are independent. If the after region executes K times, the before region executes K + 1 times. The scf.condition operands are therefore in before phase: true-cycle operands enter the after region; the single false-cycle operand tuple becomes the while result tuple.

1720

Emitted lowering skeleton:

1722
# Structural loop entry and loop-back control. This exists even when
# the source while has no data inits.
%iter_ctrl = carry %cond, %entry_ctrl, %after_done : none

# Each before block argument is loop-carried in before phase.
%a_i = carry %cond, %init_i, %a_next_i : A_i

# The before region consumes %iter_ctrl and %a_i..., then produces:
#   %cond        : i1
#   %b_j         : B_j, one stream per scf.condition trailing operand
#   %before_done : none, the tail of before-region side effects

# scf.condition true operands enter after; false operands are results.
# Lane convention: lane 0 = false, lane 1 = true.
%b_exit_j, %b_after_j =
  demux %cond, %b_j : (i1, B_j) -> (B_j, B_j)

# The recursively lowered before exit is projected with the same selector.
%while_done, %unused_true =
  demux %cond, %before_done : (i1, none) -> (none, none)
%after_phase, %after_ctrl =
  gate %cond, %before_done : (i1, none) -> (i1, none)

# The after region consumes %after_ctrl and %b_after_j..., then
# produces:
#   %a_next_i... : A_i, the scf.yield operands
#   %after_done  : none, the after-region completion token; if the
#                  region has no side effects and no extra control-only
#                  work, this may be %after_ctrl

%res_j = %b_exit_j
1756
  • %cond is the i1 token computed by the before-region's scf.condition. There is no stream op here; an arbitrary i1 stream produced by before-region computation drives the loop.
  • The before-region executes once more than the after-region. Demuxing the before exit gives exactly K after permissions and one while exit. The final false before execution is therefore part of loop completion.
  • %b_exit_j becomes the loop result. The same selector projects values, execution, and memory-frontier components into matching phases.
  • Each %a_next_i has length K, one value from each after-region execution. dataflow.carry consumes a next value only with cond=true; cond=false closes and resets the carry without consuming feedback.
  • Before-region invariants use the before-phase %cond stream. After-region-only invariants are replayed in before phase and projected through a true-lane demux. This keeps zero-trip loops from producing an after-only value.
  • Each touched partition has independent write-frontier and read-frontier carries following the same structure as %iter_ctrl. Before starts from their outputs. True lanes enter after and feed the next before activation; false lanes are the loop exits. This preserves memory effects performed by the final condition-checking iteration.
  • A static stream endpoint inside either region is projected by this same recurrence and may fire once per dynamic execution of its enclosing region. Multiple endpoint sites wholly inside one before or after block form a finite local schedule for each execution of that block. A schedule that crosses the before/after boundary, or combines an endpoint repeated by the while with an endpoint outside that while, requires an online ordered-event transfer. Nested conditional endpoint schedules likewise require a hierarchical event projection: an inner selector does not fire when its outer lane is inactive. Until those transfers have one canonical owner, only the affected Spatial candidate is rejected; unrelated graphs and ownership candidates remain valid.
1789

For K = 2, the dynamic sequence is:

1791
before0: cond0 = true,  b0 -> after0
after0:  yield a1
before1: cond1 = true,  b1 -> after1
after1:  yield a2
before2: cond2 = false, b2 -> while result
1799

The corresponding token lengths are:

1801
Stream Tokens
%cond [T, T, F]
%a_i [a0, a1, a2]
%b_j [b0, b1, b2]
%b_after_j [b0, b1]
%b_exit_j [b2]
%after_ctrl [before_done0, before_done1]
%while_done [before_done2]
%a_next_i [a1, a2]
1812

The final %cond = false token is consumed without %a_next_i or %after_done. It emits no new before value and returns each carry to its init state. Independent write-frontier and read-frontier carries follow the same selector contract in docs/spec-compiler-part-3-mem.md.

1817

While Boundary Translation

1819

This translation uses condition-driven carry rings for execution, source inits, and each touched W_P/R_P component. Carry outputs enter before directly. After before is lowered, the false lanes are the while execution, result, and frontier exits. dataflow.gate projects execution and captured values into after phase; true condition-argument and frontier lanes enter after through their selector-matched projections. After exits feed the next before activation.

1827

Before therefore executes K + 1 times when after executes K. The final false before effects are included in the outgoing pair. A final-false read updates R_P at loop exit and a following write must wait for it. False does not consume dummy feedback.

1832

6.3 scf.for with scf.yield

1834

There are two distinct cases.

1836

No Iter Args

1838

Source:

1840
scf.for %i = %c0 to %n step %c1 {
  %x = memref.load %A[%i] : memref<?xi32>
  memref.store %x, %B[%i] : memref<?xi32>
}
1847

Lowering:

1849
# Source scf.for IVs are typed `index`. dataflow.stream requires its
# %init / %limit / %step / iv stream to share a scalar signless integer
# type (see docs/spec-dataflow-part-1-streaming.md). The lowering
# therefore inserts arith.index_cast at the boundary: %lb / %ub /
# %step are cast from index to a chosen iN, and the body IV %i is
# cast back to index before memref indexing. The chosen iN is Loom's
# configured index-width integer type.

%lb_iN, %ub_iN, %step_iN  = arith.index_cast %lb, %ub, %step : index to iN
%i_iN, %loop_phase = stream %lb_iN, %ub_iN, %step_iN
                      step add while slt : iN
%i = arith.index_cast %i_iN : iN to index
# body memory and address computation consume %i directly

# Source-sequential execution recurrence and zero-trip exit:
%ctrl_raw = carry %loop_phase, %ctrl_in, %body_done : none
%loop_exit_ctrl, %body_ctrl =
  demux %loop_phase, %ctrl_raw : (i1, none) -> (none, none)
1870

For N dynamic body executions:

1872
Stream Length Meaning
%loop_phase N + 1 N true tokens plus one false close
%i_iN / %i N body induction values
%ctrl_raw N + 1 initial permission plus body feedback
%body_ctrl N source-sequential body permissions
%loop_exit_ctrl 1 structured exit token
1880

The no-result case has no data loop result to compute. The stream emits exactly one IV per body execution and no IV for the close transition. The recursively lowered body returns %body_done, which authorizes the next source iteration. Memory leaves additionally wait on their partition frontiers as specified in docs/spec-compiler-part-3-mem.md. Loop-invariant memref operands are not replayed with dataflow.invariant; they remain memory bindings on the lowered loads and stores.

1889

With Iter Args

1891

Source:

1893
%sum = scf.for %i = %c0 to %n step %c1
          iter_args(%acc = %init) -> i32 {
  %x = memref.load %A[%i] : memref<?xi32>
  %next = arith.addi %acc, %x : i32
  scf.yield %next : i32
}
1902

Lowering:

1904
# Same IV index<->iN cast pattern as the No Iter Args case, see
# the lowering above.
%lb_iN, %ub_iN, %step_iN  = arith.index_cast %lb, %ub, %step : index to iN
%i_iN, %loop_phase = stream %lb_iN, %ub_iN, %step_iN
                      step add while slt : iN
%i = arith.index_cast %i_iN : iN to index

%acc_raw = carry %loop_phase, %init, %next : i32

%acc_exit, %acc_body =
  demux %loop_phase, %acc_raw : (i1, i32) -> (i32, i32)

# body executes only in body phase
%x = dataflow.load %A[%i], ... : memref<?xi32>
%next = arith.addi %acc_body, %x : i32

%sum = %acc_exit
1924

The iter-arg state stream is deliberately in loop phase, not body phase. carry sees %loop_phase, so it emits an N + 1 state stream: the initial value, then one carried value after each true iteration. The same %loop_phase demuxes that state stream. The true lane produces exactly N %acc_body values and the false lane produces exactly one %acc_exit value used as the loop result.

1931

The feedback to carry has length N: %next is produced once per true iteration. On the final false phase, carry consumes no next value, emits no additional state, and returns to its init state.

1935

For N = 0:

1937
Stream Tokens
%loop_phase [F]
%i []
%acc_raw [init]
%acc_body []
%next []
%acc_exit [init]
%sum init
1947

For N = 1:

1949
Stream Tokens
%loop_phase [T, F]
%i [0]
%acc_raw [init, next0]
%acc_body [init]
%next [next0]
%acc_exit [next0]
%sum next0
1959

For N = 2:

1961
Stream Tokens
%loop_phase [T, T, F]
%i [0, 1]
%acc_raw [init, next0, next1]
%acc_body [init, next0]
%next [next0, next1]
%acc_exit [next1]
%sum next1
1971

Multiple iter_args lower independently using the same pattern, one carry / demux state ring per iter_arg. Body operations may freely combine the body-lane values from multiple iter_args before feeding the corresponding yielded values directly to their carries. Memref operands are not iter_arg-like stream state; only explicit none memory-order state is carried for memory dependences.

1978
  • For each touched memory partition, the loop has independent hidden none carries for W_P and R_P. Both are initialized from the incoming frontier pair, driven by %loop_phase, sent to the body on the true lane, and returned as loop exits on the false lane. The zero-trip case forwards both initial components.
1984

For Boundary Translation

1986

This translation uses one loop selector from dataflow.stream and independent carry -> demux rings for execution, iter_args, and each touched W_P/R_P component. True lanes enter the recursively lowered body; false lanes are loop exits. Captured non-memref values use invariant followed by true-lane projection.

1992

The body feeds every ring independently. Zero trip produces only the false selector token, so init execution, values, and frontier components transfer unchanged. Read-only state does not create RAR order; write feedback preserves RAW, WAR, and WAW across source-sequential iterations.

1997

6.4 scf.forall

1999

Accepted Input Contract

2001

The Ownership Materialization And Handoff section of docs/spec-compiler-part-2-scf.md is the sole normative owner of forall normalization. Before Part 3 begins, the selected Structured Program Candidate must have:

2006
  • materialized a forall selected as an AccCore thread domain as a canonical dataflow.thread definition and dataflow.thread.launch;
2008
  • retained a graph-owned forall only as a mapping-free, effect-form, compile-time fixed-domain construct whose P[] width, ownership, and cross-lane legality are materialized in semantic IR and can be re-proved;MS1V858 · MS1V8 linked-input-79 · linked input 79generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:20eb96f51c52af18e8b31078 with its governing context; report any stag5000 generated · 5000 pairedopen PBT →MS1V357 · MS1V3 linked-input-79 · linked input 79generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:d1dc2174ff936b5d9497d7eb with its governing context; report any stag5000 generated · 5000 pairedopen PBT →
2011

and

2012
  • materialized every supported aggregation or reduction into accepted semantics, or failed finalizability truthfully.MS1V860 · MS1V8 linked-input-208 · linked input 208generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:20eb96f51c52af18e8b31078 with its governing context; report any stag5000 generated · 5000 pairedopen PBT →MS1V359 · MS1V3 linked-input-208 · linked input 208generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:d1dc2174ff936b5d9497d7eb with its governing context; report any stag5000 generated · 5000 pairedopen PBT →
2015

Part 3 does not convert aggregation form, decide thread ownership, rewrite forall to parallel as an optimization policy, infer P[], serialize lanes, or

2017

select a reduction strategy. A dynamic domain, mapping attribute, shared output, result, combining action, or failed legality re-proof causes atomic failure before canonical graph publication. Cached provenance never changes this result.MS1V862 · MS1V8 linked-input-139 · linked input 139generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:20eb96f51c52af18e8b31078 with its governing context; report any stag5000 generated · 5000 pairedopen PBT →MS1V361 · MS1V3 linked-input-139 · linked input 139generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:d1dc2174ff936b5d9497d7eb with its governing context; report any stag5000 generated · 5000 pairedopen PBT →

2022

For this boundary, an accepted effect-form forall has no shared_outs, no op results, and an empty scf.forall.in_parallel terminator.MS1V864 · MS1V8 linked-input-199 · linked input 199generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:20eb96f51c52af18e8b31078 with its governing context; report any stag5000 generated · 5000 pairedopen PBT →MS1V363 · MS1V3 linked-input-199 · linked input 199generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:d1dc2174ff936b5d9497d7eb with its governing context; report any stag5000 generated · 5000 pairedopen PBT → In the example,

2024

%N must resolve to the selected candidate's compile-time fixed extent:

2026
scf.forall (%i) in (%N) {
  %x = memref.load %A[%i] : memref<?xf32>
  %y = arith.mulf %x, %x : f32
  memref.store %y, %B[%i] : memref<?xf32>
  scf.forall.in_parallel {}
}
2035

Its result is represented only by explicit side effects in the body.

2037

The following is an aggregation form and is not accepted by Part 3:

2039
%out = scf.forall (%i) in (%N)
    shared_outs(%o = %init) -> tensor<?xf32> {
  %v = compute(%i) : f32
  %slice = tensor.from_elements %v : tensor<1xf32>

  scf.forall.in_parallel {
    tensor.parallel_insert_slice %slice into %o[%i] [1] [1]
      : tensor<1xf32> into tensor<?xf32>
  }
}
2052

Part 3 rejects this form before graph mutation. It never drops the combining region or publishes a dataflow.graph that omits the aggregation. Any legal materialization belongs to the Part 2 owner; this document intentionally does not define a bufferization or combining algorithm.MS1V866 · MS1V8 linked-input-4 · linked input 4generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:20eb96f51c52af18e8b31078 with its governing context; report any stag5000 generated · 5000 pairedopen PBT →MS1V365 · MS1V3 linked-input-4 · linked input 4generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:d1dc2174ff936b5d9497d7eb with its governing context; report any stag5000 generated · 5000 pairedopen PBT →

2057

If Part 2 selects an effect-form forall as an AccCore thread domain, the input accepted by Part 3 is already the definition-and-launch carrier shape below. The rank-one source sketch is retained only to relate the source induction variable to the canonical logical-coordinate ABI; it is not a Part 3 transformation:MS1V868 · MS1V8 linked-input-49 · linked input 49generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:20eb96f51c52af18e8b31078 with its governing context; report any stag5000 generated · 5000 pairedopen PBT →MS1V367 · MS1V3 linked-input-49 · linked input 49generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:d1dc2174ff936b5d9497d7eb with its governing context; report any stag5000 generated · 5000 pairedopen PBT →

2063
scf.forall (%tx) in (%N) {
  memref.store %v, %B[%tx] : memref<?xf32>
  scf.forall.in_parallel {}
}
2070
// At module scope (sibling of callable definitions):
dataflow.thread @t_<funcSym>_<seq>(%B_arg : memref<?xf32>, ...)
    attributes { sym_visibility = "private" } {
^bb0(%B_arg : memref<?xf32>, ..., %thread_ctrl : none, %coord : index):
  // For normalized zero-based unit-step forall, source IV equals coordinate.
  memref.store %v, %B_arg[%coord] : memref<?xf32>
  dataflow.thread.yield
}

// At the original scf.forall site:
%tok = dataflow.thread.launch @t_<funcSym>_<seq>
       extents(%N) args(%B, ...)
       : (memref<?xf32>, ...) -> !dataflow.thread_token
dataflow.thread.wait %tok : !dataflow.thread_token
2087

The launch extents own an arbitrary-rank dense zero-based domain. The thread entry block has one trailing logical-coordinate argument per extent, in source dimension order. For each dimension, nonzero or dynamic source lower bounds and steps cross as ordinary operands and the body reconstructs source_iv = lower + coordinate * step. Values captured from outside the source forall likewise become ordinary launch operands and matching definition arguments. Sections 5.4.1 and 5.4.2 and docs/spec-compiler-part-4-partitioned-data.md own the complete ABI.

2096

Code inside the thread definition remains InstructionCore code unless the selected Structured Program Candidate explicitly wraps it in loom.spatial_region. That compiler-internal region remains the temporary SpatialCore ownership carrier until Part 3 atomically replaces it with a finalized dataflow.graph definition and launch.MS1V870 · MS1V8 linked-input-44 · linked input 44generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:20eb96f51c52af18e8b31078 with its governing context; report any stag5000 generated · 5000 pairedopen PBT →MS1V369 · MS1V3 linked-input-44 · linked input 44generator constraint — feeds the generated inputsSelected stage: loom-lower-forall-to-thread. Test exactly output obligation mlir-obligation:d1dc2174ff936b5d9497d7eb with its governing context; report any stag5000 generated · 5000 pairedopen PBT → Memory operations outside

2101

the region remain in the InstructionCore body. The thread token preserves the source continuation dependency through another launch dependency or an explicit dataflow.thread.wait.

2105

Forall Boundary Translation

2107

Within an explicit loom.spatial_region, an accepted graph-owned forall is recursively replicated into its already selected static lanes. Every lane starts from the same incoming execution and per-partition (W, R) frontier, and lane exits are reduced with fixed-arity all-of joins. Empty domains are identity transfers. The forall and its empty scf.forall.in_parallel terminator are removed. No forall boundary, partition id, dependence summary, or traversal order survives into canonical graph IR.

2116

6.5 scf.parallel with scf.reduce

2118

Accepted Input Contract

2120

Parallel normalization is owned exclusively by the Structured Program Candidate lineage specified by the "Ownership Materialization And Handoff" section of docs/spec-compiler-part-2-scf.md. Part 3 accepts a graph-owned scf.parallel only when it is:

2125
  • effect-form, with no op results, init values, or reduction operands and with an empty scf.reduce terminator;
  • mapping-free and nested under the selected loom.spatial_region carrier; and
  • compile-time fixed over an arbitrary-rank logical domain whose selected P[] widths and cross-lane legality are explicit in the candidate and can be re-proved from current semantics.
2133

Dynamic-width, resultful, reduction-bearing, mapped, or otherwise unproved forms fail before graph mutation. Part 3 does not choose any chunk count K, including K = 1; flatten, serialize, or partition the iteration space as policy; invent P[]; or select and inline a reduction order. Any such choice must already be materialized as accepted semantic IR by Part 2. This document intentionally defines no future upstream chunking or reduction algorithm.

2141

For an accepted one-dimensional effect-form loop, %N denotes a compile-time-resolved extent and the candidate has already selected P[] = [%N]:

2145
scf.parallel (%i) = (%c0) to (%N) step (%c1) {
  %x = memref.load %A[%i] : memref<?xf32>
  %y = arith.mulf %x, %x : f32
  memref.store %y, %B[%i] : memref<?xf32>
  scf.reduce
}
2154

scf.parallel is not a second Dataflow loop primitive. No dataflow.parallel, dataflow.reduce, reduction enum, schedule record, or parallel control op is introduced.

2158

Parallel Boundary Translation

2160

For rank r and fixed widths P[], the graph owner creates one static lane for each logical coordinate tuple in the selected Cartesian domain. Each lane starts from the same incoming execution and per-partition (W, R) frontier, substitutes its already selected source induction values, and recursively lowers the existing body. Incomparable exits are joined with fixed-arity all-of; an empty domain is an identity transfer. The Cartesian rank is not bounded by this lowering contract.

2168

Lane enumeration is an implementation detail and never creates cross-iteration program order. A failed independence or ownership re-proof causes atomic failure. No parallel boundary, coordinate tuple, P[] record, dependence summary, or traversal order survives into canonical graph IR.

2173

6.6 scf.index_switch

2175

Structured normalization replaces a zero-case scf.index_switch with its default region. Every remaining switch lowers through the same recursive selection transfer used by scf.if. The canonical graph contains only ordinary selector arithmetic and the existing dataflow.demux / dataflow.mux actors; it does not retain SCF or introduce a second switch abstraction.

2182

scf.index_switch has the same selected-region shape as scf.if, but its source selector is an arbitrary index value matched against a dense array of case constants. dataflow.mux and dataflow.demux require dense lane selectors, so lowering first normalizes the source argument to a dataflow lane id.

2188

Lane convention is a normalized lowering convention (it is not the print order of the source op, which lists case regions before the default region in the MLIR scf.index_switch op):

2192
lane 0     = default region
lane i + 1 = case region i
2197

The one-case form has two dynamic lanes and uses an i1 selector: false selects default and true selects the single case. With two or more cases, the normalized selector has index type. The zero-case form has only the default region and is eliminated before this template is applied.

2202

For two or more cases, the normalized selector is computed as ordinary data, not with dataflow.mux. A dataflow.mux is selective and would leave each unselected case-lane constant token in its queue. Across many switch invocations those leftover tokens would accumulate without bound under any bounded-buffer runtime and eventually apply backpressure. They cannot be discarded because every candidate lane remains semantically live for a later selector token. Ordinary arith.select follows all-operand firing, so it consumes every candidate lane value on each firing and leaves no residue.

2212
# Normalize arbitrary case values to dense dataflow lanes.
# Lane convention: lane 0 = default region, lane i+1 = case region i
# (this is the lowering's normalized lane order; the source op prints
# case regions before the default region in MLIR's scf.index_switch).
# demux yields default-lane first, then case 0, case 1, ...; mux
# operand order matches.
%lane0 = dataflow.constant %ctrl {const_value = 0 : index} : index
%lane = ... compare %arg to each case value and arith.select lane i+1

%default_ctrl, %case0_ctrl, %case1_ctrl, ... =
  demux %lane, %ctrl : (index, none) -> (none, none, none, ...)

# For every non-memref stream live-in %x : T used by any selected region:
%x_default, %x_case0, %x_case1, ... =
  demux %lane, %x : (index, T) -> (T, T, T, ...)

... each selected region produces one result tuple and one done token ...

%result =
  mux %lane, %r_default, %r_case0, %r_case1, ... : (index, T, T, T, ...) -> T
%done =
  mux %lane, %done_default, %done_case0, %done_case1, ...
    : (index, none, none, none, ...) -> none
2238
  • This is a generalization of scf.if's template after selector normalization. Demux routes control and non-memref live-in streams to exactly one selected region; mux collects the selected result and done token.
  • The default region participates as lane 0. Case region i participates as lane i + 1. This is different from source case values; case values are used only while computing %lane.
  • %lane is constructed to be in range [0, num_cases]: unmatched source values keep lane 0, while matched case i selects lane i + 1. No dynamic selector-out-of-range diagnostic is required at this lowering point.
  • A selected region with no memory side effect or other control-only work has its done token equal to its local ctrl token.
  • The one-case form uses i1 demux/mux with the same lane convention: false is default and true is the single case. The comparison result is an ordinary SSA stream; multiple demuxes and muxes reuse it by token broadcast.
  • Multi-result scf.index_switch lowers one result mux per result position, all driven by the same normalized selector.
  • Transient graph stream endpoints in the default and case regions reuse this exact normalized selector. The selector is synchronized with each scheduled endpoint event before its activity bit is formed, so default, one-case, and multi-case stream routing cannot disagree with the execution and memory lane selected by recursive graph lowering.
  • If a live-in is used by only some selected regions, projections for unused lanes are dead outputs and are discarded by target lowering.
  • A zero-case form is rejected atomically before recursive graph lowering. No selector, branch transfer, memory-frontier projection, or graph mutation is created for it.
2268

For cases [2, 5] and argument stream [2, 7, 5], the normalized selector stream is [1, 0, 2]:

2271
Stream Tokens
%lane [1, 0, 2]
%default_ctrl [ctrl1]
%case0_ctrl [ctrl0]
%case1_ctrl [ctrl2]
%arg_default [7]
%arg_case0 [2]
%arg_case1 [5]
%r_default [default(arg=7)]
%r_case0 [case2(arg=2)]
%r_case1 [case5(arg=5)]
%result [case2(arg=2), default(arg=7), case5(arg=5)]
%done_default [done_default0]
%done_case0 [done_case0_0]
%done_case1 [done_case1_0]
%done [done_case0_0, done_default0, done_case1_0]
2289

Index Switch Boundary Translation

2291

GraphRegionLowering normalizes the source argument once, orders lanes as default followed by source case order, and invokes the shared recursive selection transfer. That exact selector drives execution permission, projected non-memory captures, every result mux, selected execution completion, and each touched partition's W_P and R_P demux/mux pair. Each region is recursively lowered only from its lane-specific inputs, so an unselected region receives no execution or frontier token and its effects do not execute. Multiple results produce one mux per result position. A branch that does not touch a partition returns its lane-specific incoming frontier, while a selected branch contributes its recursively reduced causal frontier.

2302

6.7 scf.execute_region

2304

Structured normalization inlines a supported scf.execute_region before graph-region lowering. A residual region means the Structured Program Candidate is not finalizable and is rejected before graph mutation.

2308

Execute Region Boundary Translation

2310

After upstream inlining, the contents participate in ordinary sequential recursive lowering. No dedicated Dataflow actor or persistent region summary is required.

2314

6.8 scf.yield

2316
  • Already a thin terminator. The lowering of the parent op produces the yield's effect; the standalone yield is removed.
2319

7. Memory Frontier Model

2321

docs/spec-compiler-part-3-mem.md specifies the single recursive owner, basic graph-local alias partitions, leaf transfer equations, and independent write/read recurrence state. Section 6 of this document specifies how the same selectors project execution, values, and both frontier components at each supported SCF boundary.

2327

8. Logical Domains And Data Views

2329

docs/spec-compiler-part-4-partitioned-data.md specifies the two canonical launch domains, dynamic responsibility transfer and termination, source-IV reconstruction, and derived-view boundary. These semantics add no partition carrier or mapping attribute to SCF-to-DFG flattening.

2334

8.1 Canonical Artifact Finalization And Entity Identity

2336

Artifact Owner And Schema

2338

The Canonical Dataflow Program is the single semantic root of the fixed Artifact family:

2341
loom.canonical_dataflow 3.0
2345

Version 3.0 removes residual conversion bridges while admitting first-class LLVM pointer values through the closed pointer and address projections above. It removes pointer-to-memref launch adaptation entirely: the typed graph launch memory binding accepts only an exact memref capability relation. loom.canonical_dataflow 1.0 and 2.0 bytes are not accepted or silently upgraded; a producer must rebuild and finalize the program through the current graph ABI.

2353

The family owns its admitted module surface, canonical semantic relation graph, canonical writer, artifact-local entity catalog, and importer. Common owns only the shared Artifact envelope, schema/version framing, SHA-256 v1 identity calculation, and collision-checked publication. Mapping, simulation, Evaluation, visualization, and native caches consume Dataflow-owned references; none may assign or reinterpret a Dataflow entity ID.

2360breadth-17 · sampled attempt

Finalization is failure-atomic. It operates on a private clone of the complete program, validates every canonical graph and the whole-program thread, launch, channel, memory-root, symbol, and completion relations, constructs canonical bytes, invokes the Common finalizer, and publishes only the complete valid Artifact. There is no is_finalized operation attribute or partially finalized program state. A valid Common envelope, exact schema descriptor, canonical bytes, and successful independent family verification together define a finalized Artifact.

2369

Closed Entity Catalog

2371

The first schema has exactly five independently referenceable entity kinds:

2373
CanonicalDataflowEntityKind =
    Graph
  | Actor
  | RootThreadLaunch
  | StaticGraphLaunch
  | LogicalMemoryRoot
2382

Their carriers are:

2384
  • Graph. Each reachable finalized dataflow.graph definition.
  • Actor. Each operation in a graph body accepted as a real actor by the shared canonical Dataflow actor classifier. Structural terminators and boundary block arguments are not actors.
  • RootThreadLaunch. Each retained static dataflow.thread.launch site. Thread launches cannot occur inside another thread or graph, so every such site is a root launch.
  • StaticGraphLaunch. Each retained static dataflow.graph.launch site in a thread definition.
  • LogicalMemoryRoot. Each static imported-memory formal role and each canonical fresh-allocation definition. A view preserves an existing root and does not create another root entity.
2397

dataflow.thread definitions do not receive IDs in this schema. Every persistent use begins at a root launch and recovers the definition through its typed callee relation. Private functions, thread definitions, actor operands/results, graph boundaries, software edges, memory views, channel branches, and dynamic invocation, work-item, memory-object, or firing occurrences are likewise not independent entities. They are recovered through identified owners plus typed semantic ordinals, canonical relations, or execution-local identity. A future independently referenceable semantic object requires an explicit schema catalog change; a consumer cannot mint an ID for convenience.

2408

Absence of a thread-definition EntityId does not remove dynamic thread identity. The logical-domain contract derives each dense point from a root launch and its coordinate tuple, or each dynamic point from a root launch and its WorkItemId. Runtime adds one execution-local dispatch occurrence for a concrete invocation. A definition ID, logical point, dispatch occurrence, and physical AccCore binding are different domains and must never be substituted for one another.

2416

All five kinds share one Artifact-global unsigned 64-bit EntityId namespace. Zero is a valid ID and there is no sentinel value. The finalizer assigns the dense range [0, entity_count) in canonical-slot order, but serialized record position is not identity and consumers must resolve the explicit ID.

2421

The complete typed persistent references are:

2423
GraphRef             = (CanonicalDataflow ArtifactIdentity, Graph EntityId)
ActorRef             = (CanonicalDataflow ArtifactIdentity, Actor EntityId)
RootThreadLaunchRef   = (CanonicalDataflow ArtifactIdentity,
                         RootThreadLaunch EntityId)
StaticGraphLaunchRef  = (CanonicalDataflow ArtifactIdentity,
                         StaticGraphLaunch EntityId)
LogicalMemoryRootRef  = (CanonicalDataflow ArtifactIdentity,
                         LogicalMemoryRoot EntityId)
2434

An artifact that already binds the exact Dataflow identity may use a compact typed local ID on the wire. The full meaning still includes that binding. Wrong-kind, foreign-artifact, missing, duplicate, out-of-range, or noncanonical IDs are invalid.

2439

Closed Structural Reference Catalog

2441

Objects below the five entity kinds use closed owner-relative structural references. They do not receive another EntityId, and consumers must not replace them with symbol paths, operation positions, generic field paths, or native dense indices.

2446

A graph launch is interpreted in the context of one root thread launch:

2448
RootedGraphLaunchRef =
  (RootThreadLaunchRef, StaticGraphLaunchRef)
2453

The referenced static graph-launch site must belong to the thread definition resolved from the root launch. This context is required because one thread definition may be used by several root launches with different channel bindings, memory roots, execution bindings, or physical targets. It remains a static structural reference, not a dynamic invocation identity.

2459

Graph-local token endpoints use the following closed forms:

2461
GraphIngressTokenRef =
    Start(GraphRef)
  | ValueInput(GraphRef, value-input ordinal)
  | StreamInput(GraphRef, stream-input ordinal)

GraphEgressTokenRef =
    ValueOutput(GraphRef, value-output ordinal)
  | StreamOutput(GraphRef, stream-output ordinal)
  | CompletionFrontier(GraphRef, completion-frontier ordinal)

ActorTokenResultRef  = (ActorRef, result ordinal)
ActorTokenOperandRef = (ActorRef, operand ordinal)

CanonicalGraphProducerEndpointRef =
    GraphIngressTokenRef
  | ActorTokenResultRef

CanonicalGraphConsumerEndpointRef =
    ActorTokenOperandRef
  | GraphEgressTokenRef
2484

The Dataflow actor-port classifier must validate every actor ordinal as a token-plane port. A memory capability operand or result is never admitted by these unions. The exact producer endpoint and Dataflow def-use relation derive one complete canonical sink set. TechMapping may remove sinks proven internal to a selected realization, but it cannot change endpoint identity or create a second software-edge catalog.

2491

The thread/graph ABI exposes one-message boundary transfers separately from thread-level channels:

2494
RootThreadBoundaryTransferRef =
    Start(RootThreadLaunchRef)
  | ValueInput(RootThreadLaunchRef, value body-operand ordinal)
  | Completion(RootThreadLaunchRef)

GraphLaunchBoundaryTransferRef =
    Start(RootedGraphLaunchRef)
  | ValueInput(RootedGraphLaunchRef, value-input ordinal)
  | ValueResult(RootedGraphLaunchRef, value-result ordinal)
  | Done(RootedGraphLaunchRef)
2507

Root-thread value inputs exclude channel handles and memory capabilities. Extents and derived coordinates belong to the Thread Dispatch parameter contract rather than this message catalog. Each boundary-transfer reference owns exactly one source terminal and one sink terminal. Root start and value inputs flow from the runtime boundary to the selected InstructionCore; root completion flows back to runtime retirement. Graph start and value inputs flow from the selected InstructionCore to its SpatialCore; graph value results and done flow in the reverse direction. Explicit thread-token dependencies remain part of Thread Dispatch and do not create a second completion-message graph.

2517

Channel endpoints are:

2519
ThreadChannelSendSiteRef =
  (RootThreadLaunchRef, canonical send-site ordinal)

ThreadChannelReceiveSiteRef =
  (RootThreadLaunchRef, canonical receive-site ordinal)

ChannelProducerRef =
    GraphStreamOutput(RootedGraphLaunchRef, stream-output ordinal)
  | ThreadSend(ThreadChannelSendSiteRef)

ChannelConsumerRef =
    GraphStreamInput(RootedGraphLaunchRef, stream-input ordinal)
  | ThreadReceive(ThreadChannelReceiveSiteRef)
2535

Send and receive ordinals index Dataflow-owned canonical endpoint inventories for the rooted thread. They are not textual positions. The exact channel relation and each consumer-owned source_map derive the complete canonical consumer set for one producer. Dynamic message correspondence remains the ordered event relation specified by docs/spec-dataflow-part-1-streaming.md; no message ordinal, activation pairing, epoch, or Physical Tag enters these static references.

2543

The complete transfer-terminal unions are:

2545
CanonicalProducerTerminalRef =
    RootThreadBoundarySource(RootThreadBoundaryTransferRef)
  | GraphLaunchBoundarySource(GraphLaunchBoundaryTransferRef)
  | ChannelProducer(ChannelProducerRef)

CanonicalSinkTerminalRef =
    RootThreadBoundarySink(RootThreadBoundaryTransferRef)
  | GraphLaunchBoundarySink(GraphLaunchBoundaryTransferRef)
  | ChannelConsumer(ChannelConsumerRef)
2557

A boundary source derives its one paired sink. A channel producer derives its complete non-empty sorted consumer set. Graph value results and later graph value inputs remain two distinct InstructionCore-facing ABI transfers. A compiler that wants direct graph-to-graph streaming must represent it with a channel/stream or fuse the graphs; Mapping cannot invent that rewrite.

2563

Memory references remain on the capability plane:

2565
LogicalMemoryViewRef =
  (LogicalMemoryRootRef, canonical root-local view ordinal)

LogicalMemoryRootOrViewRef =
    Root(LogicalMemoryRootRef)
  | View(LogicalMemoryViewRef)

ContextualActorRef =
  (RootedGraphLaunchRef, ActorRef)

MemoryExposureRef =
  (RootedGraphLaunchRef, graph memory-result ordinal)

FenceActorFamilyRef =
  ActorRef validated as dataflow.fence
2583

The root-local view inventory owns each root-preserving static view relation. Instantiating one reusable graph view under different logical roots therefore produces distinct structural references in the corresponding root inventories, without allocating view entities. A contextual actor must belong to the graph called by its rooted launch. A memory exposure identifies a launch-contextual graph memory result and resolves through the Dataflow memory relation to exactly one logical root or view.

2591

System service members use one closed obligation-relative union:

2593
ServiceMemberRef =
    MessageTransfer
  | AddressedMemoryActor(ContextualActorRef)
  | FenceActor(ContextualActorRef)
2600

MessageTransfer is the singleton member of a transfer obligation, including multicast. Addressed-memory and fence members derive their exact Canonical Service kind and local legs from the actor semantics. A memory exposure is not a service member and has no request or response leg; it is a capability boundary selected by a service target binding or Mapping exposure entry.

2606

System resource-time anchors reuse transfer terminals and rooted actor transitions already owned by Dataflow:

2609
StaticTransferEventRef =
    Produced(CanonicalProducerTerminalRef)
  | Consumed(CanonicalSinkTerminalRef)

ContextualActorTransitionEventRef =
  (ContextualActorRef, transition_case_ordinal)

EventFamilyKey =
    Transfer(StaticTransferEventRef)
  | ActorTransition(ContextualActorTransitionEventRef)

EventLogicalInputSlot =
    Coordinate(event-domain coordinate ordinal)
  | LaunchParameter(root-launch parameter ordinal)

EventLogicalProjection(EventFamilyKey) =
  canonical sorted unique array<EventLogicalInputSlot>
2629

A graph-local endpoint event has one Dataflow-owned rooted projection into that same event-family domain:

2632
RootedGraphEndpointEventProjection(
  RootedGraphLaunchRef,
  Produced(CanonicalGraphProducerEndpointRef)
    | Consumed(CanonicalGraphConsumerEndpointRef)
) -> canonical nonempty set<EventFamilyKey>
2640

For Produced(ActorTokenResultRef), the set contains exactly the rooted ActorTransition cases whose OperationSchema-owned activeResults contains that result ordinal. For Consumed(ActorTokenOperandRef), it contains exactly the rooted cases whose consumedInputs contains that operand ordinal. This is an alternatives set: occurrence of any member is occurrence of the original endpoint event. Several cases producing or consuming one port do not become an AllOf relation.

2648

A graph start or value-input ingress maps to the consumed exact graph-launch boundary terminal. A stream-input ingress maps to the consumed exact channel consumer terminal. A value-output egress maps to the produced exact graph- launch result terminal, and a stream-output egress maps to the produced exact channel producer terminal. A completion-frontier egress maps recursively through its unique canonical graph producer because one frontier token is not the graph-wide done event. The recursive projection must terminate at an ingress or actor result in the acyclic SSA definition relation; malformed, empty, foreign, or duplicate results are rejected.

2658

This projection is a removable view of the Canonical Dataflow Program and its OperationSchema projections. It has no EntityId, persistent record, digest, or Mapping/Deployment override. Consumers use the existing EventFamilyKey comparison wire for its sorted unique result.

2663

There is no static-event EntityId, and the projection is not a field of the key. Every terminal and contextual actor transition resolves through the exact program to one rooted logical event domain. The transition ordinal resolves in the exact actor's canonical ActorHandshakeCase projection from OperationSchema. It is not a service-local request ID, a Mapping event, or a second transition catalog. For every addressed-memory and fence actor, the unique transition-case commit is issue of one logical operation as specified by docs/spec-dataflow-memory-consistency.md; a missing or non-unique issue transition is invalid for a System resource-time anchor.

2673

Coordinate indexes the resolved event domain's canonical coordinate inventory. LaunchParameter indexes the owning root thread launch's canonical parameter inventory. That inventory contains the launch extents in coordinate order followed by ordinary bodyOperands of index or signless-integer type in body-operand order. Channel handles, memory capabilities, tokens, pointers, floats, vectors, and aggregates are not launch parameters. For a DynamicWork launch, the designated work-item operand is also excluded because its identity is owned by the stable-item projection. Types, ranks, values, bounds, and domain membership are recovered from the exact Canonical Dataflow Program and are never copied into a slot.

2684

The event-domain coordinate inventory is likewise Dataflow-owned. A dense root boundary uses the thread coordinate suffix in source-dimension order; a graph or channel terminal uses the exact rooted event may-domain and endpoint relation. A repeated stored-program launch does not acquire a hidden iteration slot. If Mapping must distinguish such iterations, the selected program must already expose the distinction as a logical coordinate, launch parameter, or DynamicWork stable-item component.

2692

The projection contains every coordinate and launch-parameter slot available to Mapping or admission for this event. It is derived once by CanonicalDataflowProgramView; it is not selected by Mapping, Deployment, or runtime. An empty projection is valid. A missing, duplicate, wrong-owner, wrong-kind, out-of-range, or noncanonical slot is invalid. Dynamic-work stable item components remain in their separately owned stable-key projection. An event-rooted Mapping relation may consume that projection in addition to this coordinate/launch-parameter projection, but neither schema is embedded in the event key.

2702

Canonical projection order is the complete EventLogicalInputSlot key order: Coordinate before LaunchParameter, then unsigned ordinal. Variant discriminants are zero-based u32be values in declaration order, ordinals and counts are u64be, and the projection comparison wire is:

2707
u64be(slot_count)
repeat slot_count times:
  u32be(slot_kind)
  u64be(slot_ordinal)
2714

EventFamilyKey canonical comparison wire starts with the zero-based u32be declaration-order discriminant Transfer or ActorTransition. Transfer is followed by the existing StaticTransferEventRef wire: its zero-based u32be Produced or Consumed discriminant and recursively encoded terminal reference. ActorTransition is followed by the recursively encoded ContextualActorRef and the u64be transition-case ordinal. Closed-union variants use declaration-order u32be discriminants; Dataflow EntityId and owner-local ordinals use u64be; nested structural references are encoded in field order. When the containing artifact already binds the exact Canonical Dataflow identity, the local wire omits that repeated identity; a standalone reference uses the Common exact ArtifactReference framing first. There is no alternate textual, JSON-specific, or native-index ordering authority.

2727

This comparison wire fixes cross-family ordering and round-trip semantics; it does not create a parallel binary Artifact schema. A Mapping canonical assembly or Deployment canonical JSON writer renders the same typed variants and fields in its sole owner format, then rederives the comparison wire for ordering and validation.

2733

One runtime event occurrence consists of the key, concrete values matching the derived projection, any applicable separately owned DynamicWork item identity, and a transient occurrence handle. Concrete values, dynamic item identity, and the handle never enter Artifact identity, Mapping keys, channel ordering, or Physical Tag assignment. SpatialMapping-local actor activity remains owned by the SpatialMapping. System closure uses the rooted contextual form only when a selected System resource is activated by that exact actor transition; it does not copy the SpatialMapping record or create another dynamic occurrence.

2742

Canonical Semantic Relation Graph

2744

Before labeling, the finalizer removes every pre-existing dataflow.entity_id from its private clone. The relation graph contains the complete semantic program, not only the five entity nodes. It includes:

2748
  • each actor's registered OperationSchemaId, exact types, and closed schema-owned semantic property and attribute projection;
  • explicit operand/result ordinals, SSA def-use, block-successor, containment, region, boundary-segment, symbol-use, and launch-callee relations;
  • logical-memory root and root-preserving view relations, including every launch-derived imported linear view; and
  • explicit execution order in HostCore and InstructionCore stored-program regions.
2757

A dataflow.graph body is a graph region, so actor textual order contributes no relation. Module and symbol-table order are also nonsemantic. Stored-program block/control/operation order is semantic and remains in the relation graph. This distinction prevents a generic "ignore operation order" rule from silently changing InstructionCore behavior.

2763

SSA and block labels, private symbol spelling, source and filesystem locations, debug/provenance metadata, visual coordinates, printer order, and builder insertion order are excluded. Private symbols are resolved to typed relations and receive canonical printed labels. An externally visible linkage name is ABI semantics rather than a private printer label and is included together with its linkage and visibility contract. Every other registered non-actor field is semantic by default. Every actor field must be classified by its closed operation-schema projection. Excluding any field requires an explicit owner-spec rule rather than an open ignore list.

2773

The equivalence boundary is exact typed and attributed structural isomorphism. It does not prove algebraic or whole-program functional equivalence. A semantics-preserving Dataflow rewrite whose graph is non-isomorphic therefore produces a different Canonical Dataflow Artifact, as required by the Dataflow optimization lineage.

2779

Canonical labeling determines semantic slots independently of source handles. Entities in one automorphism orbit have no recoverable nonsemantic source identity. The finalizer may return a source-object-to-final-ID provenance map for the current derivation, but it does not enter canonical bytes and is not a reference authority.

2785

Materialization, Import, And Memory Instances

2787

After canonical slots are fixed, the finalizer assigns IDs and materializes the single dataflow.entity_id attribute on each entity carrier. A logical memory root carried by an ordinary function-like argument uses that argument's existing attribute dictionary. The canonical writer emits normalized private symbol, SSA, and block labels, canonical unordered collections, the derived IDs, and all semantic relations. It omits locations and the explicitly nonsemantic metadata above. The Common finalizer hashes exactly those family-owned canonical bytes.

2796

The derived IDs are excluded while recomputing canonical labels, avoiding a circular identity definition. A finalized importer independently reconstructs the relation graph and requires every materialized ID to match the canonical assignment. A mutable authoring program may omit IDs; any supplied values are discarded on the private finalization clone rather than trusted.

2802

One Dataflow-owned read-only CanonicalDataflowProgramView projects the five typed ID maps, every closed structural-reference inventory above, canonical actor and endpoint relations, rooted launch contexts, channel producer and consumer relations, launch-callee closure, logical-memory root/view and exposure relations, service-member derivation, static transfer events, and their exact EventLogicalProjection values. Its native indices and lookup tables are disposable caches. Mapping's draft/search structures and simulator event tables may cache this view, but cannot define another persistent graph, actor, launch, terminal, member, event, or memory catalog.

2813

LogicalMemoryRootRef identifies a static software root role, not one runtime object. An imported runtime object is bound by the exact launch and runtime memory registry. A fresh graph allocation instance is derived from its static root reference and graph invocation occurrence. If two imported roles alias at runtime, the runtime registry relates them to the same object without merging their static entity IDs. A memory view remains a typed structural reference whose root relation resolves to exactly one LogicalMemoryRootRef. The importer derives each admitted view from its exact root-preserving memref relation. It never searches for or executes a graph-body conversion operation.

2823

Anchor Verification

2825

Anchor-level tests cover:

2827
  • exact catalog-2.0 kind membership and ordinals, every per-kind decision-wire layout and round trip, canonical set and pair rejection, and rejection of decision-1.0 payloads rather than reinterpretation;
  • deterministic complete match enumeration under presentation reordering, including descending chunk divisors before scalarization, parent-qualified attempted-decision keys, FIFO frontier discovery, and the exact semantic limit boundary;
  • one positive edge, one decisive illegal precondition, and the necessary liveness or memory counterexample for every rewrite kind, with both directions exercised for bidirectional rules and no-op publication rejected; and
  • preservation of one-match intermediate candidates, plus identical-content deduplication and inverse-cycle termination without suppressing a legal decision on a different parent;
  • invariance under private-symbol and SSA renaming, location changes, module definition reordering, and graph actor textual reordering;
  • identity changes for actor kind, type, semantic attribute, operand ordinal, edge, stored-program order, or externally visible linkage changes;
  • one registered operation schema drives graph admission, canonical actor projection, simulator lookup, and Fabric matching, while an unclassified actor property is rejected;
  • equal canonical bytes and valid unique IDs for isomorphic symmetric inputs, without asserting a source-handle-to-slot correspondence;
  • rejection of stale, missing, duplicate, noncanonical, foreign, or wrong-kind references and unresolved symbol or root relations;
  • distinct rooted graph-launch references when one thread definition is reached from two root launches;
  • token-plane endpoint rejection for a memory-capability ordinal and out-of-range actor or boundary ordinals;
  • acceptance of exact memref launch binding and first-class pointer value and stream ports, including two typed memref views over one root;
  • rejection of pointer types in graph memory segments, pointer layouts without an exact provider, pointer-to-memref launch adaptation, noncanonical memref view formals, and residual builtin.unrealized_conversion_cast;
  • complete canonical sink derivation for one multicast channel producer;
  • rejection when a memory exposure is interpreted as a service member or assigned a service leg;
  • static transfer-event round trip without a static event entity ID, plus empty and non-empty projection ordering and wire round trip; and
  • DFG-sim actor import from an exact Canonical Dataflow Artifact without any Mapping Artifact.
2869

Tests do not pin printer whitespace, a particular graph-labeling algorithm, native container layout, source-handle provenance, or a broad operation fixture matrix.

2873

9. Verifier Rules (Front-End Specific)

2875

In addition to the Dataflow dialect and finalized-program verifier set:

2877
  • dataflow.thread (definition, Section 5.4.1)
  • The op is a Symbol-bearing, function-like callable; it must be a direct child of a ModuleOp (HasParent<"ModuleOp">).
  • sym_name is required and module-unique among dataflow.thread definitions and other Symbol-bearing ops in the same module.
  • sym_visibility is required and must equal "private" under the baseline visibility policy. "public" and "nested" are rejected unless cross-module linkage is enabled by a separate spec.
  • function_type inputs are the user body operand types (T0..TN); function_type results are empty.
  • domain is one closed Part 4 domain. For DenseRectangular, entry block argument count equals numBodyOperands + 1 + coordinateRank. For DynamicWork, the rank is zero, the count is numBodyOperands + 1, and work_item_arg_ordinal selects one ordinary input. The dense block-arg layout is (args_*, thread_ctrl, coord_*): the first N == numBodyOperands block args mirror function_type.inputs exactly, then one none-typed thread_ctrl block arg, then one index-typed block arg per logical coordinate (in source-dimension order). The coordinate suffix length is the sole definition of rank. This ordering keeps the first N block args aligned with function_type.inputs, satisfying the upstream FunctionOpInterface invariant.
  • The body is IsolatedFromAbove: every SSA value used in the body and defined outside it is rejected.
  • Body must not contain a dataflow.graph definition (a graph definition is a sibling at module scope, not a body element). A dataflow.graph.launch is the only way to invoke a graph callable from inside a thread definition's body.
  • Body must not contain a dataflow.thread definition or a dataflow.thread.launch; thread definitions are module-scope siblings and launches are caller-side only. The launch verifier checks this restriction transitively through nested regions.
  • dataflow.work.spawn is legal only in a DynamicWork definition and outside every nested dataflow.graph; its operand type equals the designated work-item input.
  • A DynamicWork definition rejects channel-typed captures, channel create, send, receive, and graph stream bindings.
  • InstructionCore code and dataflow.graph.launch ops are allowed in a thread body. An InstructionCore-only body with no graph launch is also legal; this verifier rule does not itself select AccCore execution.
  • Body may contain llvm.call or func.call only when the callee has been proven InstructionCore-legal or is scheduled for inlining before graph extraction. Body must not contain llvm.func or func.func definitions.
  • Reachability is a pipeline invariant, not a local verifier rule. The verifier may accept an unreferenced private definition as dead IR, but finalized program publication removes unreachable private symbols.

  • dataflow.thread.launch (Section 5.4.2)

  • callee resolves to a dataflow.thread definition in the same module (verifier rejects unresolved or wrong-kind callee).
  • bodyOperands types equal callee.function_type.inputs position-by-position.
  • For a dense callee, extents count equals coordinate rank. Every extent has index type. Statically known negative extents are rejected; runtime values are checked before instance creation. Dense rank zero creates one instance and any zero extent creates none. A dynamic callee has no extents and the designated body operand supplies exactly one root work item.
  • The op always produces exactly one !dataflow.thread_token result for collective retirement of all logical instances. Dynamic work retires only after its active responsibility set is empty.
  • Must appear outside every dataflow.thread and dataflow.graph definition, including through nested regions.

  • dataflow.thread.yield

  • Accepts zero or more none operands as an unordered all-of completion frontier. The parent dataflow.thread definition has no data results; the per-launch completion token is produced by the launch op, not yielded as a body value. The verifier checks only frontier operand types and terminator placement.
  • Parent op must be a dataflow.thread definition (enforced by ParentOneOf<["::dataflow::ThreadOp"]>).
  • In a dynamic definition, the terminator retires the current item exactly once after its completion frontier; it does not close a channel or retire the collective token while another responsibility remains active.

  • dataflow.work.spawn

  • Must appear transitively inside one DynamicWork thread and outside every graph. Dense threads, host code, and graph bodies reject it.
  • Its one operand type equals the definition's designated work-item input.
  • It is effectful, has no result or target, and acquires responsibility before making the child visible.

  • dataflow.thread.wait

  • At least one operand. Each is !dataflow.thread_token produced by a dataflow.thread.launch.
  • Must appear outside every dataflow.thread and dataflow.graph definition, including through nested regions.
  • The op has no SSA result and therefore produces no graph-control none value. It is an ordered stored-program causal wait, not a memory barrier.

  • dataflow.graph (definition, Section 5.5.1)

  • The op is a Symbol-bearing, function-like callable; it must be a direct child of a ModuleOp (HasParent<"ModuleOp">).
  • sym_name is required and module-unique among dataflow.graph definitions and other Symbol-bearing ops in the same module.
  • sym_visibility is required and must equal "private" in the baseline visibility policy. "public" and "nested" are rejected unless cross-module linkage is enabled by a separate spec.
  • function_type inputs are (T0..TN) and results are (R0..RM), containing only application payloads. Normalized input_segments and result_segments classify value, stream, and memory ports. The graph start and launch done endpoints are not function-type slots.
  • Every memory port is a ranked or unranked memref. LLVM pointers are legal only in value or stream segments and never become capability identity.
  • The graph definition's body is IsolatedFromAbove: every SSA value used in the body and defined outside it is rejected.
  • Entry block arguments are (%ctrl_in : none, %arg_0 : T0, ..., %arg_N : TN): the trailing arguments mirror function_type.inputs, while %ctrl_in is the explicit start protocol endpoint.
  • The body's dataflow.graph.return terminator has values, streams, memories, and mandatory non-empty complete segments. Concatenated payload segments match all function_type.results. Done is not a return payload or function-type slot.
  • Every finalized actor resolves to exactly one registered OperationSchemaId and passes its CanonicalDataflowActorOpInterface instance verifier. The derived canonical actor classifier consumes this registry for compute, control, and memory actors; it is not a separate whitelist. Lowering does not infer actor support from dialect or operation names.
  • A registered LLVM-dialect compute operation is eligible only through that same interface and only when it has explicit SSA operands and results, no regions or successors, no hidden memory, control, ABI, or runtime state, deterministic typed per-firing semantics, only DataLayout dependencies recorded by its OperationSchema projection, and explicit semantic parameters. This is the sole LLVM exception; registered arithmetic, math, scalar, and vector compute actors remain legal under the same contract. An LLVM operation that is an exact semantic alias of an available standard arith or math actor is non-canonical and must have been normalized before graph finalization. Exact fused FMA uses math.fma; a non-fused multiply-add remains two explicit actors.
  • Residual imperative LLVM surface is forbidden. This includes calls and unresolved intrinsics, inline assembly, loads, stores, atomics, fences, allocation and unregistered pointer manipulation, branches, switches, PHI nodes, memory-copy or memory-set operations, and ABI, exception, stack, or runtime operations. Supported source forms must be normalized into canonical actors and explicit event networks before finalization.
  • Body must not contain scf.*, llvm.func, func.func, llvm.call, func.call, builtin.unrealized_conversion_cast, dataflow.thread.launch, dataflow.graph.launch, dataflow.thread.wait, dataflow.graph.wait, another dataflow.graph definition, or a dataflow.thread definition.
  • The op declares RecursiveMemoryEffects so module-scope walkers can observe per-callable effects. Launch completion is still defined only by the explicit return frontier.

  • dataflow.graph.launch

  • callee resolves to a dataflow.graph definition in the same module (verifier rejects unresolved or wrong-kind callee).
  • Operand and result segments bind mechanically to the callee's normalized value, stream, and memory segments. Stream ports bind channel endpoints. Memory inputs accept only the exact memref graph launch memory binding. The finalized-program validator proves the actual root/view relation. Memory results are exact memref matches.
  • The mandatory trailing done : none result is the retirement protocol endpoint and equals all_of(callee.graph.return.complete). No effect scan or quiescence rule provides an alternate completion authority.
  • The op must appear inside a dataflow.thread definition's body, not at host scope and not inside another dataflow.graph definition's body.
  • The launch does not reconstruct completion from callee effects. Native finalization validates that the explicit return frontier covers every observable effect before mapping or simulation.

  • dataflow.graph.wait (Section 5.5.3)

  • Accepts a non-empty unordered all-of frontier of none completion events and has no result.
  • Must be transitively contained by exactly one dataflow.thread definition and must not appear at host scope or inside a dataflow.graph definition.
  • Finalized-program validation requires every operand to be a direct graph done result or a path-aware terminal event whose causal closure contains one. Generic none values and textual order are insufficient.
  • It is a stored-program graph-retirement wait, not a memory barrier, channel drain, thread-token conversion, or alternate completion authority.

  • Dataflow_GraphReturnOp

  • complete is non-empty, variadic, unordered all-of, and none-typed.
  • values, streams, and memories, in that order, match the parent payload result types.
  • A single completion witness with no stream or memory outputs may use the compact %complete, %values... syntax; all other shapes print named segments.
3066

10. Non-Goals

3068

The following are explicitly out of scope for the scf-to-dfg contract:

3071
  • Binding dataflow.thread directly to a fabric.module symbol. The thread remains target-independent software IR; SystemMapping binds logical thread execution to an AccCore, while TechMapping and SpatialMapping realize each selected graph on that AccCore's SpatialCore. The thread is already isolated and has an explicit boundary operand list.
  • Native dataflow.thread data results, async value types, thread groups, and thread-level aggregation regions. Part 2 must materialize any supported tensor-result aggregation into accepted effect form before thread promotion; a residual aggregation form fails finalizability.
  • LLVM IR provider integration, source-language integration, and clang embedding. Those concerns belong to Part 1 and Part 2.
  • Logical-domain-point to fabric-resource binding and neighborhood communication or distributed-buffer protocols. These are not part of this contract. In particular, this spec does not commit to a stencil-specific neighbor-exchange op or a default mapping from a logical coordinate to any fabric.pe or fabric.mem instance.
  • Channel routing or thread-endpoint simulation. Graph publication preserves typed stream input/output bindings and source_map while mechanically converting region-local endpoints to the canonical graph stream network. One binding owns one ordered dynamic event sequence: sequential and structured mutually exclusive static sites share a fixed ordinal schedule, with inactive choice sites filtered from that sequence. Repeated launch instances concatenate their contributions in deterministic issue order; producer and consumer events correspond by flat sequence ordinal, not by a one-to-one activation relation. A repeat nested under any selected region is refused until a conditional event-count projection can prove how inactive lanes suppress every repeated event; an invariant repeat bound alone is not such a proof. Publication does not invent routing, endpoint creation, a parallel channel mode, or a traversal order for ambiguous parallel endpoint sites.
3102

11. References

3104
  • docs/spec-fabric-module.md, docs/spec-fabric-pe.md, docs/spec-fabric-fu.md -- Fabric hardware semantics consumed by Mapping, not embedded in the Canonical Dataflow Program.
  • docs/spec-compiler-part-1-source.md -- high-level source integration and metadata emission.
  • docs/spec-compiler-part-2-scf.md -- LLVM-to-SCF raising and structured thread-boundary preparation.
  • docs/spec-compiler-part-3-mem.md -- recursive graph-region memory lowering, basic alias partitions, write/read frontier transfers, and structured recurrence used inside each dataflow.graph.
  • docs/spec-core-dialect-boundary.md -- compiler, Dataflow, Fabric, Mapping, and runtime ownership outside the canonical graph ABI.
  • docs/spec-mapping-artifact.md, docs/spec-mapping-memory.md, and docs/spec-pnr.md -- TechMapping, canonical-memory realization, and Spatial/System physical realization after canonical graph publication.
  • docs/spec-compiler-part-4-partitioned-data.md -- canonical logical launch domains, source-IV reconstruction, ordinary data views, and the physical Mapping boundary.
  • docs/spec-dataflow-part-1-streaming.md -- precise timing semantics for dataflow.stream, dataflow.carry, dataflow.invariant, and dataflow.gate.
  • docs/spec-dataflow-part-2-control.md -- precise firing semantics for dataflow.constant, dataflow.sync, dataflow.mux, and dataflow.demux.
  • Upstream MLIR references (LLVM externals/llvm/mlir/...):
  • Dialect/SCF/IR/SCFOps.td.
  • Dialect/Async/IR/AsyncOps.td, Dialect/Async/IR/AsyncTypes.td.
  • Dialect/GPU/IR/GPUOps.td, Dialect/GPU/IR/GPUBase.td.
  • Dialect/OpenMP/IR/OpenMPOps.td, Dialect/OpenACC/IR/OpenACCOps.td.
  • Conversion/SCFToGPU/SCFToGPU.cpp.