diff options
22 files changed, 207 insertions, 75 deletions
diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang index 9b55dc35a..ddee52b6b 100644 --- a/source/slang/core.meta.slang +++ b/source/slang/core.meta.slang @@ -3513,7 +3513,7 @@ __prefix T operator ~(T v0); // IR level type traits. __generic<T> -__intrinsic_op($(kIROp_Undefined)) +__intrinsic_op($(kIROp_Poison)) // Note: attempting to actually *use* the result of `__declVal()` will always be invalid. T __declVal(); __generic<T> diff --git a/source/slang/slang-emit-c-like.cpp b/source/slang/slang-emit-c-like.cpp index d69485cce..11ea2e989 100644 --- a/source/slang/slang-emit-c-like.cpp +++ b/source/slang/slang-emit-c-like.cpp @@ -1679,7 +1679,7 @@ bool CLikeSourceEmitter::shouldFoldInstIntoUseSites(IRInst* inst) // for GLSL), so we check this only after all those special cases are // considered. // - if (inst->getOp() == kIROp_Undefined) + if (as<IRUndefined>(inst)) return false; // Okay, at this point we know our instruction must have a single use. @@ -2388,7 +2388,8 @@ void CLikeSourceEmitter::defaultEmitInstExpr(IRInst* inst, const EmitOpInfo& inO case kIROp_RTTIPointerType: break; - case kIROp_Undefined: + case kIROp_LoadFromUninitializedMemory: + case kIROp_Poison: case kIROp_DefaultConstruct: m_writer->emit(getName(inst)); break; @@ -3245,7 +3246,8 @@ void CLikeSourceEmitter::_emitInst(IRInst* inst) case kIROp_LiveRangeEnd: emitLiveness(inst); break; - case kIROp_Undefined: + case kIROp_LoadFromUninitializedMemory: + case kIROp_Poison: case kIROp_DefaultConstruct: { auto type = inst->getDataType(); diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index 8bcd1429f..d5697117a 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -4512,7 +4512,8 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex case kIROp_GetStringHash: result = emitGetStringHash(inst); break; - case kIROp_Undefined: + case kIROp_LoadFromUninitializedMemory: + case kIROp_Poison: result = emitOpUndef(parent, inst, inst->getDataType()); break; case kIROp_SPIRVAsm: diff --git a/source/slang/slang-emit-vm.cpp b/source/slang/slang-emit-vm.cpp index 80d3762aa..71439ba0b 100644 --- a/source/slang/slang-emit-vm.cpp +++ b/source/slang/slang-emit-vm.cpp @@ -492,8 +492,12 @@ public: { switch (inst->getOp()) { - case kIROp_Undefined: + case kIROp_Poison: + case kIROp_LoadFromUninitializedMemory: { + // We basically handle an undefined value by allocating a + // temporary and then not initializing it. + // ensureWorkingsetMemory(funcBuilder, inst); } break; diff --git a/source/slang/slang-ir-autodiff-fwd.cpp b/source/slang/slang-ir-autodiff-fwd.cpp index 946d5e7cb..5ca277672 100644 --- a/source/slang/slang-ir-autodiff-fwd.cpp +++ b/source/slang/slang-ir-autodiff-fwd.cpp @@ -752,7 +752,7 @@ InstPair ForwardDiffTranscriber::transcribeCall(IRBuilder* builder, IRCall* orig SLANG_RELEASE_ASSERT(diffCalleeType->getParamCount() == origCall->getArgCount()); auto placeholderCall = - builder->emitCallInst(nullptr, builder->emitUndefined(builder->getTypeKind()), 0, nullptr); + builder->emitCallInst(nullptr, builder->emitPoison(builder->getTypeKind()), 0, nullptr); builder->setInsertBefore(placeholderCall); IRBuilder argBuilder = *builder; IRBuilder afterBuilder = argBuilder; @@ -2143,7 +2143,8 @@ InstPair ForwardDiffTranscriber::transcribeInstImpl(IRBuilder* builder, IRInst* case kIROp_DefaultConstruct: return transcribeDefaultConstruct(builder, origInst); - case kIROp_Undefined: + case kIROp_Poison: + case kIROp_LoadFromUninitializedMemory: return transcribeUndefined(builder, origInst); case kIROp_Reinterpret: diff --git a/source/slang/slang-ir-autodiff-primal-hoist.cpp b/source/slang/slang-ir-autodiff-primal-hoist.cpp index 5dea7f61b..eb85b9309 100644 --- a/source/slang/slang-ir-autodiff-primal-hoist.cpp +++ b/source/slang/slang-ir-autodiff-primal-hoist.cpp @@ -2667,7 +2667,8 @@ static bool shouldStoreInst(IRInst* inst) case kIROp_ExtractExistentialValue: case kIROp_ExtractExistentialType: case kIROp_ExtractExistentialWitnessTable: - case kIROp_Undefined: + case kIROp_LoadFromUninitializedMemory: + case kIROp_Poison: case kIROp_GetSequentialID: case kIROp_GetStringHash: case kIROp_Specialize: diff --git a/source/slang/slang-ir-dce.cpp b/source/slang/slang-ir-dce.cpp index 0b89baf64..9578bf5e5 100644 --- a/source/slang/slang-ir-dce.cpp +++ b/source/slang/slang-ir-dce.cpp @@ -75,7 +75,7 @@ struct DeadCodeEliminationContext } } - IRInst* getUndefInst() + IRInst* getUnitPoisonVal() { if (!undefInst) { @@ -84,7 +84,7 @@ struct DeadCodeEliminationContext builder.setInsertBefore(firstChild); else builder.setInsertInto(module->getModuleInst()); - undefInst = Slang::getUndefInst(builder, module); + undefInst = Slang::getUnitPoisonVal(builder, module); } return undefInst; } @@ -109,12 +109,20 @@ struct DeadCodeEliminationContext // markInstAsLive(root); - // Ensure there is a global undef inst that is always alive. - // This undef inst will be used to fill in weak-referencing uses - // whose used value is marked as dead and eliminated. - // We always make sure this undef inst is available to prevent - // infiniate oscilating loops. - markInstAsLive(getUndefInst()); + // We will use a single instruction representing an undefined + // value (using the `poison` instruction) as the replacement + // for any lingering uses of a value that is marked as dead + // (those uses logically represent "weak" references to the + // original value, since they didn't cause it to be kept alive). + // + // We need to make sure that this unique undefined value is + // treated as live, because if it is allowed to be removed then + // the compiler can get into an infinite oscillating loop where + // we determine that this instruction is not live and thus try + // to remove it and replace it with... a new instruction that + // is exactly like it. + // + markInstAsLive(getUnitPoisonVal()); // Marking the module as live should have // seeded our work list, so we can now start @@ -233,7 +241,7 @@ struct DeadCodeEliminationContext // if (inst->hasUses()) { - inst->replaceUsesWith(getUndefInst()); + inst->replaceUsesWith(getUnitPoisonVal()); } if (inst->getOp() == kIROp_Param) diff --git a/source/slang/slang-ir-eliminate-phis.cpp b/source/slang/slang-ir-eliminate-phis.cpp index d5f1b9e43..47f5e17f9 100644 --- a/source/slang/slang-ir-eliminate-phis.cpp +++ b/source/slang/slang-ir-eliminate-phis.cpp @@ -1038,7 +1038,7 @@ struct PhiEliminationContext // so that any logic that might have moved another parameter // into a temporary will influence our result. // - if ((*srcArg.currentValPtr)->getOp() != kIROp_Undefined) + if (!as<IRUndefined>(*srcArg.currentValPtr)) { // If we are trying to emit a store directly after a load from the same var, // skip the store. diff --git a/source/slang/slang-ir-glsl-legalize.cpp b/source/slang/slang-ir-glsl-legalize.cpp index 1511bbff2..1ecae6574 100644 --- a/source/slang/slang-ir-glsl-legalize.cpp +++ b/source/slang/slang-ir-glsl-legalize.cpp @@ -3529,7 +3529,7 @@ void legalizeEntryPointParameterForGLSL( // operations like `TraceRay` are handled. // builder->setInsertBefore(func->getFirstBlock()->getFirstOrdinaryInst()); - auto undefinedVal = builder->emitUndefined(pp->getFullType()); + auto undefinedVal = builder->emitPoison(pp->getFullType()); pp->replaceUsesWith(undefinedVal); return; diff --git a/source/slang/slang-ir-insts-stable-names.lua b/source/slang/slang-ir-insts-stable-names.lua index a34dc346a..923e73ac4 100644 --- a/source/slang/slang-ir-insts-stable-names.lua +++ b/source/slang/slang-ir-insts-stable-names.lua @@ -156,7 +156,7 @@ return { ["Constant.blob_constant"] = 152, ["CapabilitySet.capabilityConjunction"] = 153, ["CapabilitySet.capabilityDisjunction"] = 154, - ["undefined"] = 155, + ["Undefined.Poison"] = 155, ["defaultConstruct"] = 156, ["MakeDifferentialPairBase.MakeDiffPair"] = 157, ["MakeDifferentialPairBase.MakeDiffPairUserCode"] = 158, @@ -680,4 +680,5 @@ return { ["SymbolAlias"] = 676, ["Decoration.InParamProxyVar"] = 677, ["Attr.MemoryScope"] = 678, + ["Undefined.LoadFromUninitializedMemory"] = 679, } diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h index 2255afc67..43adbcb64 100644 --- a/source/slang/slang-ir-insts.h +++ b/source/slang/slang-ir-insts.h @@ -2794,17 +2794,6 @@ struct IRRTTIObject : IRInst FIDDLE(leafInst()) }; -// An instruction that yields an undefined value. -// -// Note that we make this an instruction rather than a value, -// so that we will be able to identify a variable that is -// used when undefined. -FIDDLE() -struct IRUndefined : IRInst -{ - FIDDLE(leafInst()) -}; - // Special inst for targets that support default initialization, // like the braces '= {}' in C/HLSL FIDDLE() @@ -4277,7 +4266,8 @@ public: IRInst* emitGpuForeach(List<IRInst*> args); - IRUndefined* emitUndefined(IRType* type); + IRLoadFromUninitializedMemory* emitLoadFromUninitializedMemory(IRType* type); + IRPoison* emitPoison(IRType* type); IRInst* emitReinterpret(IRInst* type, IRInst* value); IRInst* emitOutImplicitCast(IRInst* type, IRInst* value); diff --git a/source/slang/slang-ir-insts.lua b/source/slang/slang-ir-insts.lua index e21fc86ae..ac72e718a 100644 --- a/source/slang/slang-ir-insts.lua +++ b/source/slang/slang-ir-insts.lua @@ -504,7 +504,47 @@ local insts = { }, }, { CapabilitySet = { hoistable = true, { capabilityConjunction = {} }, { capabilityDisjunction = {} } } }, - { undefined = {} }, + + -- Instructions that represent something with an undefined value. + { Undefined = { + + -- A load from a memory location that is known to be uninitialized. + -- + -- Primarily used so that the compiler front-end can diagnose an error on such cases. + -- + -- A given `LoadFromUninitializedMemory` might evaluate to an arbitrary value of its type, + -- and an optimization pass may freely decide on a particular value to use and replace + -- all uses of the instruction with that value. + -- + -- If there are multiple distinct `LoadFromUninitializedMemory` instructions, then they + -- might each yield a different value, even if they all reference the same memory + -- location. + -- + -- Akin to `freeze(undefined)` in LLVM. + -- + { LoadFromUninitializedMemory = {} }, + + -- An undefined value that is infectious. + -- + -- Semantically, a poison value of some type T can be thought of as a + -- hypothetical out-of-band instance of type T, akin to a T-specific NaN + -- value (although a poison `float` is distinct from a `float` NaN value...). + -- The motivation for this interpretation is that it allows most optimizations + -- to ignore the possibility of poison/undefined values, while still being + -- semantically correct. + -- + -- In most cases, an instruction that is executed with a poison value as one + -- of its operands yields a poison value as its result. The main exception + -- is instructions that only conditionally use an operand, such as `select`, + -- and block/function parameters (just because one branch passes a poison + -- argument for a parmeter, that doesn't mean the parameter would be poison + -- every time the block executes). + -- + -- Corresponds to the LLVM `poison` instruction. + -- + { Poison = {} }, + }}, + -- A `defaultConstruct` operation creates an initialized -- value of the result type, and can only be used for types -- where default construction is a meaningful thing to do. diff --git a/source/slang/slang-ir-legalize-empty-array.cpp b/source/slang/slang-ir-legalize-empty-array.cpp index 1e6798a2e..76e1874b7 100644 --- a/source/slang/slang-ir-legalize-empty-array.cpp +++ b/source/slang/slang-ir-legalize-empty-array.cpp @@ -50,9 +50,30 @@ struct EmptyArrayLoweringContext return ptr && isEmptyArray(ptr->getValueType()); } - // Visit each instruction and replace it with legalized instructiosn if necessary. + // Visit each instruction and replace it with legalized instructions if necessary. void processInst(IRInst* inst) { + // + // This function is handling several different replacements at once: + // + // * Instructions that would create an empty array value are changed + // to create a unit value (`void`) instead. + // + // * Instruction that would try to read an element from an empty array, + // or form a pointer to such an element, instead yield a `poison` + // (undefined) value. Note that they do not yield `LoadFromUninitializedMemory` + // because there is no guarantee that there would be anything in the + // memory corresponding to an index into an empty array. + // + // * Instructions that would try to read from an undefined pointer, + // buffer, image, etc. yield `poison`. + // + // * Instructions that would try to write to an undefined pointer, + // buffer, image, etc. are skipped (the behavior is undefined, so + // "do nothing" is a valid choice for the behavior, which has the + // nice side effect of potentially rendering other code redundant). + // + IRInst* replacement = nullptr; IRBuilder builder(module); @@ -65,64 +86,64 @@ struct EmptyArrayLoweringContext [&](IRGetElement* getElement) { const auto base = getElement->getBase(); - return hasEmptyArrayType(base) ? builder.emitUndefined(getElement->getDataType()) + return hasEmptyArrayType(base) ? builder.emitPoison(getElement->getDataType()) : nullptr; }, [&](IRGetElementPtr* gep) { const auto base = gep->getBase(); return hasEmptyArrayPtrType(gep) || hasEmptyArrayPtrType(base) || - base->getOp() == kIROp_Undefined - ? builder.emitUndefined(gep->getDataType()) + as<IRUndefined>(base) + ? builder.emitPoison(gep->getDataType()) : nullptr; }, [&](IRFieldAddress* gep) { const auto base = gep->getBase(); - return hasEmptyArrayPtrType(gep) || base->getOp() == kIROp_Undefined - ? builder.emitUndefined(gep->getDataType()) + return hasEmptyArrayPtrType(gep) || as<IRUndefined>(base) + ? builder.emitPoison(gep->getDataType()) : nullptr; }, [&](IRLoad* load) { - return load->getOperand(0)->getOp() == kIROp_Undefined - ? builder.emitUndefined(load->getDataType()) + return as<IRUndefined>(load->getOperand(0)) + ? builder.emitPoison(load->getDataType()) : nullptr; }, [&](IRImageLoad* load) { - return load->getOperand(0)->getOp() == kIROp_Undefined - ? builder.emitUndefined(load->getDataType()) + return as<IRUndefined>(load->getOperand(0)) + ? builder.emitPoison(load->getDataType()) : nullptr; }, [&](IRStore* store) { - if (store->getPtr()->getOp() == kIROp_Undefined) + if (as<IRUndefined>(store->getPtr())) store->removeAndDeallocate(); return nullptr; }, [&](IRAtomicStore* store) { - if (store->getPtr()->getOp() == kIROp_Undefined) + if (as<IRUndefined>(store->getPtr())) store->removeAndDeallocate(); return nullptr; }, [&](IRImageStore* store) { - if (store->getImage()->getOp() == kIROp_Undefined) + if (as<IRUndefined>(store->getImage())) store->removeAndDeallocate(); return nullptr; }, [&](IRImageSubscript* subscript) { - return subscript->getImage()->getOp() == kIROp_Undefined - ? builder.emitUndefined(subscript->getDataType()) + return as<IRUndefined>(subscript->getImage()) + ? builder.emitPoison(subscript->getDataType()) : nullptr; }, [&](IRAtomicOperation* atomic) { - return atomic->getOperand(0)->getOp() == kIROp_Undefined - ? builder.emitUndefined(atomic->getDataType()) + return as<IRUndefined>(atomic->getOperand(0)) + ? builder.emitPoison(atomic->getDataType()) : nullptr; }, // The following should match any instruction which can construct a 0-sized array. diff --git a/source/slang/slang-ir-legalize-types.cpp b/source/slang/slang-ir-legalize-types.cpp index 021566e12..c6e8383c8 100644 --- a/source/slang/slang-ir-legalize-types.cpp +++ b/source/slang/slang-ir-legalize-types.cpp @@ -1952,6 +1952,18 @@ static LegalVal legalizeDefaultConstruct(IRTypeLegalizationContext* context, Leg static LegalVal legalizeUndefined(IRTypeLegalizationContext* context, IRInst* inst) { + // HACK: We check here if an undefined value we are trying to legalize is + // of an opaque type (such as a texture, buffer, or sampler), and issue + // a diagnostic (since there is really no way to make such code compile + // correctly for may of our downstream compilers). + // + // TODO(tfoley): This is emphatically *not* the right place for a diagnostic + // like this to be issued. We need rigorous checking for use of undefined + // values in the front-end. Once the front-end's checking can be trusted, + // the back-end should be able to remove any code that appears to use + // an uninitialized value of opaque type, since such code would have undefined + // behavior anyway. + // IRType* opaqueType = nullptr; if (isOpaqueType(inst->getFullType(), &opaqueType)) { @@ -1962,6 +1974,18 @@ static LegalVal legalizeUndefined(IRTypeLegalizationContext* context, IRInst* in context->m_sink->diagnose(loc, Diagnostics::useOfUninitializedOpaqueHandle, opaqueType); } + + // It is not ideal, but this pass legalizes an undefined value to... nothing. + // + // As a result, in any context that tries to consume a `LegalVal` based on its type, + // we need to be prepared not only for a value that matches the structure of that type, + // but also the possibility of an empty value like this. + // + // TODO(tfoley): We should *either* have a distinct case of `LegalVal` to represent + // undefined-ness, or this code should follow the same flow as the other cases by + // recursively decomposing the structure of the instruction's type and building up + // a `LegalVal` that just happens to have undefined values at its leaves. + // return LegalVal(); } @@ -2044,7 +2068,8 @@ static LegalVal legalizeInst( case kIROp_Printf: result = legalizePrintf(context, args); break; - case kIROp_Undefined: + case kIROp_LoadFromUninitializedMemory: + case kIROp_Poison: return legalizeUndefined(context, inst); case kIROp_GpuForeach: // This case should only happen when compiling for a target that does not support diff --git a/source/slang/slang-ir-peephole.cpp b/source/slang/slang-ir-peephole.cpp index 8d6685212..79dfc1360 100644 --- a/source/slang/slang-ir-peephole.cpp +++ b/source/slang/slang-ir-peephole.cpp @@ -1041,7 +1041,7 @@ struct PeepholeContext : InstPassBase continue; SLANG_ASSERT(terminator->getArgCount() > paramIndex); auto arg = terminator->getArg(paramIndex); - if (arg->getOp() == kIROp_Undefined) + if (as<IRUndefined>(arg)) continue; if (argValue == nullptr) argValue = arg; @@ -1268,14 +1268,18 @@ struct PeepholeContext : InstPassBase } case kIROp_Load: { - // Load from undef is undef. - if (as<IRLoad>(inst)->getPtr()->getOp() == kIROp_Undefined) + // An attempt to load from an undefined pointer value + // is undefined behavior and the resulting value is poison + // (the value should typically contaminate instructions that + // use it, rendering them as poisonous). + // + if (as<IRUndefined>(as<IRLoad>(inst)->getPtr())) { IRBuilder builder(module); IRBuilderSourceLocRAII srcLocRAII(&builder, inst->sourceLoc); builder.setInsertBefore(inst); - auto undef = builder.emitUndefined(inst->getDataType()); + auto undef = builder.emitPoison(inst->getDataType()); inst->replaceUsesWith(undef); maybeRemoveOldInst(inst); changed = true; @@ -1284,8 +1288,17 @@ struct PeepholeContext : InstPassBase } case kIROp_Store: { - // Store undef is no-op. - if (as<IRStore>(inst)->getVal()->getOp() == kIROp_Undefined) + // An attempt to store to an undefined pointer value is + // undefined behavior (just like a load), so we can conveniently + // decide to implement that behavior as a no-op. + // + // TODO: While it is not the responsibility of a pass like this + // to diagnose errors (that is the front-end's job), it might + // be best to replace an invalid `store` like this with an + // instruction that represents a "panic" or similar exceptional + // situation. + // + if (as<IRUndefined>(as<IRStore>(inst)->getVal())) { maybeRemoveOldInst(inst); changed = true; @@ -1294,8 +1307,16 @@ struct PeepholeContext : InstPassBase } case kIROp_DebugValue: { - // Update debug value with undef is no-op. - if (as<IRDebugValue>(inst)->getValue()->getOp() == kIROp_Undefined) + // Attempting to update the debug value of a variable with an + // undefined value will be treated as a no-op (meaning that the + // contents of the variable, as perceived by the user, will not + // change). + // + // TODO: We should probably validate that this is a reasonable + // behavior. In many cases a debugger user might like to have an + // indication of when the contents of their variable are undefined. + // + if (as<IRUndefined>(as<IRDebugValue>(inst)->getValue())) { maybeRemoveOldInst(inst); changed = true; diff --git a/source/slang/slang-ir-specialize.cpp b/source/slang/slang-ir-specialize.cpp index 0f8672531..7ee2d4095 100644 --- a/source/slang/slang-ir-specialize.cpp +++ b/source/slang/slang-ir-specialize.cpp @@ -982,7 +982,7 @@ struct SpecializationContext shouldSkip = true; break; } - if (item->getOperand(i)->getOp() == kIROp_Undefined) + if (as<IRUndefined>(item->getOperand(i))) { shouldSkip = true; break; diff --git a/source/slang/slang-ir-ssa.cpp b/source/slang/slang-ir-ssa.cpp index 789ca4796..dda72c813 100644 --- a/source/slang/slang-ir-ssa.cpp +++ b/source/slang/slang-ir-ssa.cpp @@ -690,10 +690,11 @@ IRInst* readVarRec(ConstructSSAContext* context, SSABlockInfo* blockInfo, IRVar* // We would only reach this function (`readVarRec`) if // a local lookup in the block had already failed, so - // at this point we are dealing with an undefined value. + // at this point we are dealing with a read from a variable + // that has not yet been initialized. auto type = var->getDataType()->getValueType(); - val = blockInfo->builder.emitUndefined(type); + val = blockInfo->builder.emitLoadFromUninitializedMemory(type); cloneRelevantDecorations(var, val); } else if (!multiplePreds) @@ -775,8 +776,13 @@ IRInst* readVar(ConstructSSAContext* context, SSABlockInfo* blockInfo, IRVar* va // searching, because its value cannot have been // established in a predecessor block. // + // We already tried and failed to find a value set + // into the variable in this block, so we are left + // to conclude that the variable is uninitialized + // at this point. + // auto type = var->getDataType()->getValueType(); - val = blockInfo->builder.emitUndefined(type); + val = blockInfo->builder.emitLoadFromUninitializedMemory(type); cloneRelevantDecorations(var, val); writeVar(context, blockInfo, var, val); return val; diff --git a/source/slang/slang-ir-use-uninitialized-values.cpp b/source/slang/slang-ir-use-uninitialized-values.cpp index 200f29d1b..368f366c3 100644 --- a/source/slang/slang-ir-use-uninitialized-values.cpp +++ b/source/slang/slang-ir-use-uninitialized-values.cpp @@ -35,7 +35,7 @@ static bool isUninitializedValue(IRInst* inst) // Also consider var since it does not // automatically mean it will be initialized // (at least not as the user may have intended) - return (inst->m_op == kIROp_Undefined) || (inst->m_op == kIROp_Var); + return (as<IRUndefined>(inst) || (inst->m_op == kIROp_Var)); } static bool isUnmodifying(IRFunc* func) diff --git a/source/slang/slang-ir-util.cpp b/source/slang/slang-ir-util.cpp index 5745ef85f..11eb4dfa0 100644 --- a/source/slang/slang-ir-util.cpp +++ b/source/slang/slang-ir-util.cpp @@ -1171,13 +1171,13 @@ bool canInstHaveSideEffectAtAddress(IRGlobalValueWithCode* func, IRInst* inst, I return false; } -IRInst* getUndefInst(IRBuilder builder, IRModule* module) +IRInst* getUnitPoisonVal(IRBuilder builder, IRModule* module) { IRInst* undefInst = nullptr; for (auto inst : module->getModuleInst()->getChildren()) { - if (inst->getOp() == kIROp_Undefined && inst->getDataType() && + if (inst->getOp() == kIROp_Poison && inst->getDataType() && inst->getDataType()->getOp() == kIROp_VoidType) { undefInst = inst; @@ -1188,7 +1188,7 @@ IRInst* getUndefInst(IRBuilder builder, IRModule* module) { auto voidType = builder.getVoidType(); builder.setInsertAfter(voidType); - undefInst = builder.emitUndefined(voidType); + undefInst = builder.emitPoison(voidType); } return undefInst; } diff --git a/source/slang/slang-ir-util.h b/source/slang/slang-ir-util.h index c12be51d6..0495bda0f 100644 --- a/source/slang/slang-ir-util.h +++ b/source/slang/slang-ir-util.h @@ -237,7 +237,10 @@ bool isPtrLikeOrHandleType(IRInst* type); bool canInstHaveSideEffectAtAddress(IRGlobalValueWithCode* func, IRInst* inst, IRInst* addr); -IRInst* getUndefInst(IRBuilder builder, IRModule* module); +/// Get a unit-type (aka `void`) value using the `poison` instruction, +/// which indicates an undefined (and potentially unstable) value. +/// +IRInst* getUnitPoisonVal(IRBuilder builder, IRModule* module); // The the equivalent op of (a op b) in (b op' a). For example, a > b is equivalent to b < a. So (<) // ==> (>). diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp index 8371d6ef5..061edd966 100644 --- a/source/slang/slang-ir.cpp +++ b/source/slang/slang-ir.cpp @@ -3384,13 +3384,18 @@ IRInst* IRBuilder::emitGetValueFromBoundInterface(IRType* type, IRInst* boundInt return inst; } - -IRUndefined* IRBuilder::emitUndefined(IRType* type) +IRLoadFromUninitializedMemory* IRBuilder::emitLoadFromUninitializedMemory(IRType* type) { - auto inst = createInst<IRUndefined>(this, kIROp_Undefined, type); - + auto inst = + createInst<IRLoadFromUninitializedMemory>(this, kIROp_LoadFromUninitializedMemory, type); addInst(inst); + return inst; +} +IRPoison* IRBuilder::emitPoison(IRType* type) +{ + auto inst = createInst<IRPoison>(this, kIROp_Poison, type); + addInst(inst); return inst; } @@ -8681,8 +8686,11 @@ bool IRInst::mightHaveSideEffects(SideEffectAnalysisOptions options) case kIROp_LiveRangeStart: case kIROp_LiveRangeEnd: + // Undefined values are treated as having no side effects. + case kIROp_LoadFromUninitializedMemory: + case kIROp_Poison: + case kIROp_Nop: - case kIROp_Undefined: case kIROp_DefaultConstruct: case kIROp_Specialize: case kIROp_LookupWitnessMethod: diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index 80aa6066a..2708c0192 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -2028,7 +2028,7 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower operands.add(argVal); }); - auto undefined = getBuilder()->emitUndefined(operands[1]->getFullType()); + auto undefined = getBuilder()->emitPoison(operands[1]->getFullType()); return getBuilder()->getDifferentialPairUserCodeType(primalType, undefined); } else |
