{"entries":[{"file_sha256":"bfc1e646e91fe0ba6d7d16e43994100b05c3b8ff79c2e07288e8955c43d9d79d","kind":"documentation_input","lines":"177-186","path":"docs/spec-compiler-part-2-scf.md","roles":["context","applicability"],"text":"FMA normalization is semantic rather than name based. An exact fused LLVM FMA\nbecomes `math.fma`. `llvm.intr.fmuladd` remains unchanged in S0 until one typed\n`ExecutionShape` decision materializes either `Fused` or\n`Split(arith.mulf, arith.addf)` under the exact floating environment and\nfast-math contract. That decision is candidate lineage and may be evaluated as\na performance choice; target code generation cannot choose it implicitly. The\nOwnership generator selects the Spatial region but does not own this decision.\nThe ExecutionShape generator resolves it before Schedule or Dataflow lowering\nmay consume the candidate. After materialization, no `fmuladd` operation may\nremain in a finalizable Sn or be registered as a Canonical Dataflow actor.","why":"Governing context of the sampled obligation: the unresolved choice is spelled llvm.intr.fmuladd in S0 and is resolved by one typed ExecutionShape decision; fixes that the output governed by the claim is the program after that materialization."},{"file_sha256":"bfc1e646e91fe0ba6d7d16e43994100b05c3b8ff79c2e07288e8955c43d9d79d","kind":"documentation_input","lines":"1016-1034","path":"docs/spec-compiler-part-2-scf.md","roles":["input_construction","input_well_formedness"],"text":"### Structured ExecutionShape Generator\n\nThe current Structured ExecutionShape generator consumes a finite set of exact\nStructured Program references. An empty input set produces an empty output\nset. A parent with no unresolved selected-Spatial execution-shape choice passes\nthrough unchanged. A parent containing an unresolved, exactly representable\n`llvm.intr.fmuladd` emits the canonical pair of complete Structured children:\n\n```text\nFused -> math.fma\nSplit -> arith.mulf followed by arith.addf\n```\n\nOne decision applies uniformly to every unresolved `fmuladd` in the selected\nSpatial ownership of that complete parent. It never rewrites residual\nInstructionCore operations or operations owned by nested callables. This is a\ntwo-element semantic policy domain, not one independent Boolean dimension per\noperation. Distinct per-operation combinations are not part of the current\ncontract.","why":"Structured ExecutionShape generator input contract: a finite set of exact Structured Program references, parents with no unresolved choice pass through unchanged, and a parent holding an unresolved exactly representable fmuladd emits the Fused/Split pair with one decision applied uniformly per parent. Drives the sampled parent mix and the single per-run shape option."},{"file_sha256":"2b0705aba1c16c80e5338d443989a9cdcbac47a60c140bff5b886c617a9f9a8f","kind":"implementation","lines":"1-33,55-115,148-181","path":"lib/Frontend/Raising/MaterializeFMulAddPass.cpp","roles":["applicability","input_construction"],"text":"// Materialize the execution shape of `llvm.intr.fmuladd`.\n//\n// `llvm.intr.fmuladd` is not a computation, it is an unmade choice: the target\n// may contract it into one fused multiply-add with a single rounding, or\n// evaluate an ordinary multiply followed by an ordinary add with two. The two\n// results differ, so nothing downstream may pick one implicitly and no shape\n// can be inferred from the intrinsic spelling. Mechanical raising therefore\n// leaves the intrinsic alone, and this pass materializes exactly the one shape\n// its caller selected:\n//\n// Fused -> math.fma\n// Split -> arith.mulf then arith.addf\n//\n// The two shapes differ in what they permit, not only in what they spell.\n// Fused carries the complete source fast-math contract onto the one fused\n// operation. Split consumes the source's `contract` permission: the multiply\n// and the add each round on their own, and neither may be contracted back\n// into a single rounding by a later pass or by target code generation.\n//\n// The selected shape is the entire decision this pass makes, so it is a\n// required typed option rather than a defaulted one, in the same shape as the\n// typed Dataflow rewrite catalog.\n//\n// A materialization is legal only when the target operations restate the whole\n// source computation: exact numeric types, the operation's fast-math contract,\n// the default floating-point environment the intrinsic is evaluated in, and\n// the enclosing callable's floating-point environment. `math.fma` and the\n// `arith` floating operations state no environment of their own, so a callable\n// stating one that they cannot restate cannot receive either shape.\n//\n// Representability is intrinsic-local. An intrinsic whose complete semantics\n// the selected standard form cannot restate remains explicit; it does not\n// prevent representable siblings from receiving the selected shape.\n// Replace one proved-representable intrinsic with the selected shape. The\n// replacement keeps the source location and the exact operand and result\n// types, and carries the source fast-math flags the selected shape still\n// permits.\nvoid materializeOne(::mlir::LLVM::FMulAddOp op, FMulAddExecutionShape shape,\n ::mlir::IRRewriter &rewriter) {\n rewriter.setInsertionPoint(op);\n ::mlir::Location loc = op.getLoc();\n ::mlir::Type type = op.getRes().getType();\n ::mlir::arith::FastMathFlags fastmath =\n loom::raising::exactFastMathFlags(op.getFastmathFlags());\n // No materialized operation states a rounding mode. An arith or math\n // operation that states one is a constrained operation: standard lowering\n // turns it into `llvm.intr.experimental.constrained.*` under an explicit\n // rounding and exception mode, and drops the fast-math flags on the way.\n // llvm.intr.fmuladd is an ordinary non-constrained intrinsic in the default\n // environment, so both shapes leave the mode absent and lower back to\n // ordinary LLVM floating operations.\n if (shape == FMulAddExecutionShape::Fused) {\n // Fusing is what the shape decided, so the complete source contract,\n // `contract` included, carries onto the one fused operation.\n rewriter.replaceOpWithNewOp<::mlir::math::FmaOp>(\n op, type, op.getA(), op.getB(), op.getC(), fastmath);\n return;\n }\n\n // `contract` is the source's permission to fuse this multiply and add into\n // one rounding. Selecting Split is the decision that declines it, so the\n // permission is consumed here rather than restated on the result: a\n // multiply and an add that still carried it would let any later contraction\n // -- upstream's own arith-to-math.fma uplift, or a backend -- re-fuse them\n // and silently undo the shape. Every other source flag is a property of the\n // computation, not of fusion, and carries onto both operations unchanged.\n ::mlir::arith::FastMathFlags split = ::mlir::arith::bitEnumClear(\n fastmath, ::mlir::arith::FastMathFlags::contract);\n\n auto product =\n ::mlir::arith::MulFOp::create(rewriter, loc, type, op.getA(), op.getB());\n product.setFastmath(split);\n auto sum = ::mlir::arith::AddFOp::create(rewriter, loc, type,\n product.getResult(), op.getC());\n sum.setFastmath(split);\n rewriter.replaceOp(op, sum);\n}\n\nvoid appendRepresentable(\n ::mlir::Operation *operation,\n ::llvm::SmallVectorImpl<::mlir::LLVM::FMulAddOp> &selected) {\n auto fmuladd = ::mlir::dyn_cast<::mlir::LLVM::FMulAddOp>(operation);\n if (fmuladd && loom::raising::restatesExactly(fmuladd.getOperation(),\n /*floating=*/true))\n selected.push_back(fmuladd);\n}\n\nvoid materializeSelected(::mlir::MLIRContext &context,\n ::llvm::ArrayRef<::mlir::LLVM::FMulAddOp> selected,\n FMulAddExecutionShape shape) {\n ::mlir::IRRewriter rewriter(&context);\n for (::mlir::LLVM::FMulAddOp op : selected)\n materializeOne(op, shape, rewriter);\n}\n // The shape is the decision, so there is no default: silently choosing one\n // would materialize a form the caller never selected.\n ::mlir::Pass::Option shape{\n *this, \"shape\",\n ::llvm::cl::desc(\"execution shape to materialize for llvm.intr.fmuladd\"),\n ::llvm::cl::values(\n clEnumValN(FMulAddExecutionShape::Fused, \"fused\",\n \"one math.fma with a single rounding\"),\n clEnumValN(FMulAddExecutionShape::Split, \"split\",\n \"an arith.mulf followed by an arith.addf\"))};\n\n void runOnOperation() final {\n if (!shape.hasValue()) {\n getOperation()->emitError(\n \"loom-materialize-fmuladd requires an explicit 'shape' option\");\n return signalPassFailure();\n }\n\n ::llvm::SmallVector<::mlir::LLVM::FMulAddOp> selected;\n (void)loom::raising::forEachCallableRegion(\n getOperation(), [&](::mlir::Region ®ion) {\n (void)loom::raising::forEachOwnedOperation(\n region, [&](::mlir::Operation *op) {\n appendRepresentable(op, selected);\n return ::mlir::WalkResult::advance();\n });\n return ::mlir::success();\n });\n\n if (selected.empty())\n return markAllAnalysesPreserved();\n\n materializeSelected(getContext(), selected, shape.getValue());\n }","why":"The subject stage itself: 'shape' is a required typed option with no default (so the bare flag fails), the pass collects only representable llvm.intr.fmuladd inside callable regions, and rewrites each to math.fma or arith.mulf+arith.addf. Establishes the correct invocation and which inputs the stage acts on."},{"file_sha256":"fc8794a0235430f2ab7b87b0fb63e4991acfa052ab630501b484fce956154a56","kind":"verifier","lines":"35-52,54-161","path":"lib/Frontend/Raising/ExactStandardSpelling.h","roles":["input_well_formedness"],"text":"inline bool isExactNumericType(::mlir::Type type) {\n if (auto vectorType = ::mlir::dyn_cast<::mlir::VectorType>(type)) {\n if (vectorType.isScalable())\n return false;\n type = vectorType.getElementType();\n }\n if (auto integerType = ::mlir::dyn_cast<::mlir::IntegerType>(type))\n return integerType.isSignless() && integerType.getWidth() > 0;\n return ::mlir::isa<::mlir::IndexType, ::mlir::FloatType>(type);\n}\n\ninline bool allExactNumericTypes(::mlir::ValueRange values) {\n for (::mlir::Value value : values) {\n if (!isExactNumericType(value.getType()))\n return false;\n }\n return true;\n}\ninline bool\nstatesDefaultDenormalEnvironment(::mlir::LLVM::DenormalFPEnvAttr env) {\n using Kind = ::mlir::LLVM::DenormalModeKind;\n return env.getDefaultOutputMode() == Kind::IEEE &&\n env.getDefaultInputMode() == Kind::IEEE &&\n env.getFloatOutputMode() == Kind::IEEE &&\n env.getFloatInputMode() == Kind::IEEE;\n}\n\n// True when `funcOp` states a floating-point policy that no standard MLIR\n// operation restates.\n//\n// An unflagged standard floating operation means the pinned default LLVM\n// floating-point environment, and neither arith nor math states an enclosing\n// environment of its own. The typed attributes read below are compared against\n// that default directly. A reciprocal-estimate policy names the operations the\n// target may compute as an estimate plus refinement rather than exactly, so\n// any such policy blocks a rewrite. The importer's generic passthrough storage\n// is classified separately because it also contains unrelated LLVM function\n// and code-generation attributes.\ninline bool passthroughEntryStatesFloatingPolicy(::mlir::Attribute entry) {\n ::llvm::StringRef name;\n ::std::optional<::llvm::StringRef> value;\n if (auto nameAttr = ::mlir::dyn_cast<::mlir::StringAttr>(entry)) {\n name = nameAttr.getValue();\n } else if (auto pair = ::mlir::dyn_cast<::mlir::ArrayAttr>(entry);\n pair && pair.size() == 2) {\n auto nameAttr = ::mlir::dyn_cast<::mlir::StringAttr>(pair[0]);\n auto valueAttr = ::mlir::dyn_cast<::mlir::StringAttr>(pair[1]);\n if (!nameAttr || !valueAttr)\n return true;\n name = nameAttr.getValue();\n value = valueAttr.getValue();\n } else {\n return true;\n }\n\n // The LLVM importer places every function attribute that LLVMFuncOp does\n // not model explicitly in one passthrough array. LLVM enum attributes still\n // retain their stable native spelling there. Of those function attributes,\n // strictfp alone changes the floating execution environment; the others\n // describe effects, control, ABI, or code generation without changing the\n // meaning of an ordinary floating instruction.\n const ::llvm::Attribute::AttrKind kind =\n ::llvm::Attribute::getAttrKindFromName(name);\n if (kind != ::llvm::Attribute::None)\n return kind == ::llvm::Attribute::StrictFP;\n\n // These string attributes are emitted by ordinary Clang compilation but\n // are not all modeled as typed LLVMFuncOp fields by the pinned importer.\n // Keep this list closed: an unknown string attribute may carry target\n // floating semantics and therefore fails closed.\n const bool codegenOnly = ::llvm::StringSwitch(name)\n .Cases({\"min-legal-vector-width\",\n \"stack-protector-buffer-size\",\n \"target-cpu\"},\n true)\n .Default(false);\n if (codegenOnly)\n return false;\n\n // Clang's default -ffp-exception-behavior=ignore spelling. A false or\n // malformed value cannot be represented by an unconstrained arith/math op.\n if (name == \"no-trapping-math\")\n return !value || *value != \"true\";\n\n return true;\n}\n\ninline bool statesFloatingPolicy(::mlir::LLVM::LLVMFuncOp funcOp) {\n if (auto env = funcOp.getDenormalFpenvAttr())\n if (!statesDefaultDenormalEnvironment(env))\n return true;\n if (auto noSignedZeros = funcOp.getNoSignedZerosFpMathAttr())\n if (noSignedZeros.getValue())\n return true;\n if (auto contraction = funcOp.getFpContractAttr())\n if (contraction.getValue() != \"off\")\n return true;\n if (funcOp.getReciprocalEstimatesAttr())\n return true;\n if (auto passthrough = funcOp.getPassthroughAttr())\n for (::mlir::Attribute entry : passthrough)\n if (passthroughEntryStatesFloatingPolicy(entry))\n return true;\n return false;\n}\n\n// True when the enclosing callable states a floating-point environment the\n// standard operation cannot restate.\ninline bool enclosingFloatingPolicyBlocksRewrite(::mlir::Operation *op) {\n auto funcOp = ::mlir::dyn_cast_or_null<::mlir::LLVM::LLVMFuncOp>(\n getNearestCallableOp(op));\n return funcOp && statesFloatingPolicy(funcOp);\n}\n\n// True when every operand and the single result of `op` have an exact standard\n// counterpart and, for a computation that reads or produces a floating value,\n// the enclosing callable states no environment the standard operation cannot\n// restate. An integer computation is independent of that environment and is\n// never blocked by it.\ninline bool restatesExactly(::mlir::Operation *op, bool floating) {\n if (!allExactNumericTypes(op->getOperands()))\n return false;\n if (!isExactNumericType(op->getResult(0).getType()))\n return false;\n return !floating || !enclosingFloatingPolicyBlocksRewrite(op);\n}","why":"Acceptance implementation of 'exactly representable': exact standard numeric types (floats, fixed-shape vectors; scalable vectors excluded) and the closed classifier for the enclosing callable's floating environment. Determines which llvm.func attribute envelopes the grammar may sample while keeping the input well formed for the claim."},{"file_sha256":"d1315fabeb736f07fdd93eca093e20d05a201967b181a40cb02f37048ba2c79a","kind":"implementation","lines":"29-95","path":"lib/Frontend/Raising/CallableRegions.h","roles":["input_construction","input_well_formedness"],"text":"inline bool isCallableOp(::mlir::Operation *op) {\n return ::mlir::isa<::mlir::LLVM::LLVMFuncOp, ::mlir::func::FuncOp>(op);\n}\n\n// Return the nearest callable that owns `op`, or null when `op` is outside\n// every callable region. A nested callable cuts off ownership inherited from\n// any callable above it.\ninline ::mlir::Operation *getNearestCallableOp(::mlir::Operation *op) {\n for (::mlir::Operation *parent = op->getParentOp(); parent;\n parent = parent->getParentOp()) {\n if (isCallableOp(parent))\n return parent;\n }\n return nullptr;\n}\n\n// Run `transform` on every non-empty region of every callable reachable from\n// `root`, visiting nested callables before their ancestors and stopping at the\n// first failure.\n//\n// Callable regions are the sole subject of mechanical raising. An imported\n// llvm.func owns its body and its complete ABI envelope, so raising rewrites\n// that body where it stands instead of copying the function into another\n// dialect to obtain a pass wrapper. A region outside a callable, such as an\n// llvm.mlir.global initializer, carries no recoverable control flow and must\n// stay expressible as an LLVM constant, so it is never rewritten.\ninline ::mlir::LogicalResult forEachCallableRegion(\n ::mlir::Operation *root,\n ::llvm::function_ref<::mlir::LogicalResult(::mlir::Region &)> transform) {\n ::mlir::WalkResult walked =\n root->walk<::mlir::WalkOrder::PostOrder>([&](::mlir::Operation *op) {\n if (!isCallableOp(op))\n return ::mlir::WalkResult::advance();\n for (::mlir::Region ®ion : op->getRegions()) {\n if (region.empty())\n continue;\n if (failed(transform(region)))\n return ::mlir::WalkResult::interrupt();\n }\n return ::mlir::WalkResult::advance();\n });\n return walked.wasInterrupted() ? ::mlir::failure() : ::mlir::success();\n}\n\n// Offer `visit` to every operation `region` owns, recursing into nested\n// regions that belong to the same callable -- scf.for, scf.if, a graph\n// region -- but stopping at any nested callable, whose own body this region\n// must not claim to own.\n//\n// This is the operation-level half of callable ownership: a callable processes\n// exactly the operations its nearest enclosing callable owns, and a nested\n// callable's body is left to that callable's own region-level walk. Crossing\n// into a nested callable here would visit its operations twice -- once\n// descended into from the enclosing region and once from the callable's own\n// walk -- so the nested callable is pruned instead. Pruning happens in\n// pre-order: in a post-order walk a callable's body is visited before the\n// callable itself, so the skip would arrive one descent too late.\ninline ::mlir::WalkResult forEachOwnedOperation(\n ::mlir::Region ®ion,\n ::llvm::function_ref<::mlir::WalkResult(::mlir::Operation *)> visit) {\n return region.walk<::mlir::WalkOrder::PreOrder>(\n [&](::mlir::Operation *op) -> ::mlir::WalkResult {\n if (isCallableOp(op))\n return ::mlir::WalkResult::skip();\n return visit(op);\n });\n}","why":"Defines the callable parents of an S0 program (llvm.func and func.func) and the ownership walk that prunes nested callables, which is what the sampled nested func.func/builtin.module/llvm.func parent exercises."},{"file_sha256":"6f55dfe3ca2a9edcd0d955b965b5011a8010478cf28db89485df1fdfb2750c9c","kind":"test","lines":"1-10,91-126","path":"test/raise/fmuladd-materialization.mlir","roles":["input_construction","context"],"text":"// RUN: split-file %s %t\n// RUN: not loom-raise-opt --loom-materialize-fmuladd %t/choice.mlir 2>&1 | FileCheck %s --check-prefix=UNSELECTED\n// RUN: loom-raise-opt --loom-materialize-fmuladd=shape=fused %t/choice.mlir | FileCheck %s --check-prefix=FUSED\n// RUN: loom-raise-opt --loom-materialize-fmuladd=shape=split %t/choice.mlir | FileCheck %s --check-prefix=SPLIT\n// RUN: loom-raise-opt --loom-materialize-fmuladd=shape=fused %t/choice.mlir | mlir-opt --convert-math-to-llvm --convert-arith-to-llvm | FileCheck %s --check-prefix=FUSED-LLVM --implicit-check-not=constrained\n// RUN: loom-raise-opt --loom-materialize-fmuladd=shape=split %t/choice.mlir | mlir-opt --convert-math-to-llvm --convert-arith-to-llvm | FileCheck %s --check-prefix=SPLIT-LLVM --implicit-check-not=constrained\n// RUN: loom-raise-opt --loom-materialize-fmuladd=shape=split %t/choice.mlir | mlir-opt --math-uplift-to-fma | FileCheck %s --check-prefix=SPLIT-KEPT --implicit-check-not=math.fma\n// RUN: loom-raise-opt --loom-materialize-fmuladd=shape=fused %t/unrepresentable.mlir | FileCheck %s --check-prefix=SCOPED\n// RUN: loom-raise-opt --loom-materialize-fmuladd=shape=fused %t/nested.mlir | FileCheck %s --check-prefix=NESTED --implicit-check-not=llvm.intr.fmuladd\n// RUN: loom-raise-opt --loom-lower-for-to-graph --mlir-disable-threading %t/selected-fused.mlir | FileCheck %s --check-prefix=SELECTED-FUSED --implicit-check-not=loom.spatial_region\n//--- choice.mlir\nllvm.func @chosen(%x: f32, %y: f32, %z: f32) -> f32 {\n %r = llvm.intr.fmuladd(%x, %y, %z)\n {fastmathFlags = #llvm.fastmath} : (f32, f32, f32) -> f32\n llvm.return %r : f32\n}\n\nllvm.func @vector_chosen(%x: vector<4xf32>, %y: vector<4xf32>,\n %z: vector<4xf32>) -> vector<4xf32> {\n %r = llvm.intr.fmuladd(%x, %y, %z)\n : (vector<4xf32>, vector<4xf32>, vector<4xf32>) -> vector<4xf32>\n llvm.return %r : vector<4xf32>\n}\n\n//--- unrepresentable.mlir\nllvm.func @representable(%x: f32, %y: f32, %z: f32) -> f32 {\n %r = llvm.intr.fmuladd(%x, %y, %z) : (f32, f32, f32) -> f32\n llvm.return %r : f32\n}\n\nllvm.func @estimated(%x: f32, %y: f32, %z: f32) -> f32\n attributes {reciprocal_estimates = \"all\"} {\n %r = llvm.intr.fmuladd(%x, %y, %z) : (f32, f32, f32) -> f32\n llvm.return %r : f32\n}\n\n//--- nested.mlir\nfunc.func @native_owner(%a: f32, %b: f32, %c: f32) -> f32 {\n builtin.module {\n llvm.func @inner(%x: f32, %y: f32, %z: f32) -> f32 {\n %r = llvm.intr.fmuladd(%x, %y, %z) : (f32, f32, f32) -> f32\n llvm.return %r : f32\n }\n }\n return %a : f32\n}","why":"Fixes the concrete invocation spelling (bare flag is the error case; =shape=fused and =shape=split are the selections) and accepted input spellings for llvm.intr.fmuladd with fastmathFlags, vector operands, an unrepresentable envelope, and the nested-callable module."},{"file_sha256":"a7ed02dd0bc477975d2b6755e3f7a0fc843775eb371999dd18fc02b6e06c64d9","kind":"example","lines":"89-122","path":"test/raise/preserved-semantics.mlir","roles":["input_well_formedness"],"text":"//--- environment.mlir\nllvm.func @default_environment(%a: f32, %b: f32) -> f32 {\n %0 = llvm.fadd %a, %b : f32\n llvm.return %0 : f32\n}\n\nllvm.func @estimated(%a: f32, %b: f32) -> f32\n attributes {reciprocal_estimates = \"all\"} {\n %0 = llvm.fadd %a, %b : f32\n llvm.return %0 : f32\n}\n\nllvm.func @flushed(%a: f32, %b: f32, %i: i32) -> f32\n attributes {denormal_fpenv = #llvm.denormal_fpenv<\n default_output_mode = preservesign, default_input_mode = ieee,\n float_output_mode = ieee, float_input_mode = ieee>} {\n %0 = llvm.fadd %a, %b : f32\n %1 = llvm.add %i, %i : i32\n llvm.return %0 : f32\n}\n\nllvm.func @contracted(%a: f32, %b: f32) -> f32\n attributes {fp_contract = \"fast\"} {\n %0 = llvm.fmul %a, %b : f32\n llvm.return %0 : f32\n}\n\nllvm.func @ordinary_clang_envelope(%a: f32, %b: f32) -> f32\n attributes {passthrough = [\"nofree\", \"norecurse\", \"nosync\",\n [\"min-legal-vector-width\", \"0\"], [\"no-trapping-math\", \"true\"],\n [\"stack-protector-buffer-size\", \"8\"], [\"target-cpu\", \"generic-rv64\"]]} {\n %0 = llvm.fadd %a, %b : f32\n llvm.return %0 : f32\n}","why":"Accepted spellings of llvm.func floating-environment attributes (denormal_fpenv, fp_contract, reciprocal_estimates, ordinary Clang passthrough envelope) used to spell only environments that keep the sampled fmuladd exactly representable."}],"primary_bundle_sha256":"635e1ded89f470aacc6c62a990f2ff11ca246b1b2754e69819070d214183e85e","project":"PolyArch/loom","revision":"48615bc5925ef4b9db8b4550b5d4322933cf4b7b","schema":"spectriad.authoring-context/v1","selection_sha256":"1bb9f10281e1b48cba0f656e803e2789a0925173c0de09c1f4c0ed1c2a906968"}