[debugger] Add DWARF typed stack entry

Add support for typed stack entries in the DWARF expression evaluator.
This allows DWARF expressions to explicitly do signed, unsigned, or
floating-point math.

This does not actually implement the new DW_OP_*_type or the casts that
can actually generate typed stack entries. That will be done in a
followup (it requires more plumbing that will be easier to implement and
review separately).

Instead, the new typed entries are tested by explicitly pushing stack
entries and then running operators over them.

Bug: 79529

Change-Id: I5dac0f87f2ea0550a8284870e204764d821666f2
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/693432
Reviewed-by: Dangyi Liu <dangyi@google.com>
Commit-Queue: Brett Wilson <brettw@google.com>
diff --git a/src/developer/debug/zxdb/client/call_site_symbol_data_provider.cc b/src/developer/debug/zxdb/client/call_site_symbol_data_provider.cc
index 948b22f..c695e1a 100644
--- a/src/developer/debug/zxdb/client/call_site_symbol_data_provider.cc
+++ b/src/developer/debug/zxdb/client/call_site_symbol_data_provider.cc
@@ -77,12 +77,16 @@
     if (eval.GetResultType() == DwarfExprEval::ResultType::kData)
       return cb(Err("DWARF expression produced unexpected results."), {});
 
+    DwarfStackEntry result = eval.GetResult();
+    if (!result.TreatAsUnsigned())
+      return cb(Err("DWARF expression produced unexpected results."), {});
+    auto result_value = result.unsigned_value();
+
     // The register value should be at the top of the stack. We could trim the stack entry to match
-    // the byte width of the register, but this is expected to be used to prvide data back to the
+    // the byte width of the register, but this is expected to be used to provide data back to the
     // DwarfExprEval which will pad it out again. So always pass all the bytes.
-    std::vector<uint8_t> bytes(sizeof(DwarfExprEval::StackEntry));
-    DwarfExprEval::StackEntry result = eval.GetResult();
-    memcpy(bytes.data(), &result, sizeof(DwarfExprEval::StackEntry));
+    std::vector<uint8_t> bytes(sizeof(result_value));
+    memcpy(bytes.data(), &result_value, sizeof(result_value));
 
     cb(Err(), std::move(bytes));
   };
diff --git a/src/developer/debug/zxdb/client/call_site_symbol_data_provider_unittest.cc b/src/developer/debug/zxdb/client/call_site_symbol_data_provider_unittest.cc
index 60d2508..eb74f59 100644
--- a/src/developer/debug/zxdb/client/call_site_symbol_data_provider_unittest.cc
+++ b/src/developer/debug/zxdb/client/call_site_symbol_data_provider_unittest.cc
@@ -130,11 +130,11 @@
   loop().RunUntilNoTasks();
   ASSERT_TRUE(result_data);
 
-  // Currently the result of the expression is the size of a StackEntry which is normally different
-  // than the requested register. This is good enough for our use-case. We validate that to make
-  // sure it's not some truncated value. The code may change to match the requested register size,
-  // in which case the expected length should be 8.
-  EXPECT_EQ(sizeof(DwarfExprEval::StackEntry), result_data->size());
+  // Currently the result of the expression is the size of a stack entry unsigned type which is
+  // normally different than the requested register. This is good enough for our use-case. We
+  // validate that to make sure it's not some truncated value. The code may change to match the
+  // requested register size, in which case the expected length should be 8.
+  EXPECT_EQ(sizeof(DwarfStackEntry::UnsignedType), result_data->size());
   result_data->resize(sizeof(kEntryRegValue));  // Trim high 0's.
 
   // Validate the result.
diff --git a/src/developer/debug/zxdb/client/frame_impl.cc b/src/developer/debug/zxdb/client/frame_impl.cc
index 17f254b..58350ef 100644
--- a/src/developer/debug/zxdb/client/frame_impl.cc
+++ b/src/developer/debug/zxdb/client/frame_impl.cc
@@ -260,12 +260,13 @@
   // Binding |this| here is OK because the DwarfExprEval is owned by us and won't give callbacks
   // after it's destroyed.
   auto save_result = [this](DwarfExprEval* eval, const Err&) {
-    if (eval->is_success()) {
-      computed_base_pointer_ = eval->GetResult();
-    } else {
-      // We don't currently report errors for frame base requests, but instead just fall back on
-      // what was computed by the backend.
-      computed_base_pointer_ = 0;
+    // We don't currently report errors for frame base requests, but instead just fall back on
+    // what was computed by the backend.
+    computed_base_pointer_ = 0;
+    if (eval->is_success() && eval->GetResultType() == DwarfExprEval::ResultType::kValue) {
+      DwarfStackEntry result = eval->GetResult();
+      if (result.TreatAsUnsigned())
+        computed_base_pointer_ = result.unsigned_value();
     }
 
     // Issue callbacks for everybody waiting. Moving to a local here prevents weirdness if a
diff --git a/src/developer/debug/zxdb/client/process_symbol_data_provider.cc b/src/developer/debug/zxdb/client/process_symbol_data_provider.cc
index 962f71a..2195e0e 100644
--- a/src/developer/debug/zxdb/client/process_symbol_data_provider.cc
+++ b/src/developer/debug/zxdb/client/process_symbol_data_provider.cc
@@ -153,7 +153,7 @@
     // makes things a little simpler) because we want to get the exact stack value (this is called
     // in the context of another DwarfExprEval).
     auto dwarf_eval = std::make_shared<DwarfExprEval>();
-    dwarf_eval->Push(static_cast<DwarfExprEval::StackEntry>(debug_address));
+    dwarf_eval->Push(DwarfStackEntry(debug_address));
     dwarf_eval->Eval(this_ref, symbol_context, DwarfExpr(std::move(program)),
                      [dwarf_eval, cb = std::move(cb)](DwarfExprEval*, const Err& err) mutable {
                        // Prevent the DwarfExprEval from getting reentrantly deleted from within its
@@ -167,11 +167,16 @@
                          return cb(err);
                        }
 
+                       const char kNotPointer[] = "TLS DWARF expression did not produce a pointer.";
                        if (dwarf_eval->GetResultType() != DwarfExprEval::ResultType::kPointer) {
-                         return cb(Err("TLS DWARF expression did not produce a pointer."));
+                         return cb(Err(kNotPointer));
+                       }
+                       DwarfStackEntry result = dwarf_eval->GetResult();
+                       if (!result.TreatAsUnsigned()) {
+                         return cb(Err(kNotPointer));
                        }
 
-                       cb(static_cast<uint64_t>(dwarf_eval->GetResult()));
+                       cb(static_cast<uint64_t>(result.unsigned_value()));
                      });
   });
 }
diff --git a/src/developer/debug/zxdb/common/BUILD.gn b/src/developer/debug/zxdb/common/BUILD.gn
index dd896a1..6901683e 100644
--- a/src/developer/debug/zxdb/common/BUILD.gn
+++ b/src/developer/debug/zxdb/common/BUILD.gn
@@ -52,6 +52,7 @@
     "file_util.cc",
     "host_util.cc",
     "inet_util.cc",
+    "int128_t.cc",
     "scoped_temp_file.cc",
     "string_util.cc",
     "tagged_data.cc",
diff --git a/src/developer/debug/zxdb/common/int128_t.cc b/src/developer/debug/zxdb/common/int128_t.cc
new file mode 100644
index 0000000..a1b5b3e5
--- /dev/null
+++ b/src/developer/debug/zxdb/common/int128_t.cc
@@ -0,0 +1,27 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/developer/debug/zxdb/common/int128_t.h"
+
+#include <limits>
+
+#include "src/developer/debug/zxdb/common/string_util.h"
+
+namespace zxdb {
+
+std::string to_string(uint128_t i) {
+  // We have a hex printer for 128-bit values which we use for values greater than 64-bits.
+  // Otherwise we need to write more code here to custom-process the numbers.
+  if (i > std::numeric_limits<uint64_t>::max())
+    return to_hex_string(i);
+  return std::to_string(static_cast<uint64_t>(i));
+}
+
+std::string to_string(int128_t i) {
+  if (i < 0)
+    return "-" + to_string(static_cast<uint128_t>(-i));
+  return to_string(static_cast<uint128_t>(i));
+}
+
+}  // namespace zxdb
diff --git a/src/developer/debug/zxdb/common/int128_t.h b/src/developer/debug/zxdb/common/int128_t.h
index 186dd59..5f8901e 100644
--- a/src/developer/debug/zxdb/common/int128_t.h
+++ b/src/developer/debug/zxdb/common/int128_t.h
@@ -5,11 +5,17 @@
 #ifndef SRC_DEVELOPER_DEBUG_ZXDB_COMMON_INT128_T_H_
 #define SRC_DEVELOPER_DEBUG_ZXDB_COMMON_INT128_T_H_
 
+#include <string>
+
 namespace zxdb {
 
 using int128_t = __int128;
 using uint128_t = unsigned __int128;
 
+// std::to_string version for this type (the standard library doesn't define it).
+std::string to_string(int128_t i);
+std::string to_string(uint128_t i);
+
 }  // namespace zxdb
 
 #endif  // SRC_DEVELOPER_DEBUG_ZXDB_COMMON_INT128_T_H_
diff --git a/src/developer/debug/zxdb/expr/eval_dwarf_expr.cc b/src/developer/debug/zxdb/expr/eval_dwarf_expr.cc
index 560e519..b6f9abb 100644
--- a/src/developer/debug/zxdb/expr/eval_dwarf_expr.cc
+++ b/src/developer/debug/zxdb/expr/eval_dwarf_expr.cc
@@ -26,9 +26,12 @@
     // the variable since it will strip "const" and stuff that the user will expect to see.
     fxl::RefPtr<Type> concrete_type = context->GetConcreteType(type.get());
 
-    // The DWARF expression produced the exact value (it's not in memory).
+    // The DWARF expression produced the exact value (it's not in memory). The expression may (but
+    // usually won't) be annotated with its own type which should match the size of the type we
+    // expect here. We don't bother validating that for now and instead just copy out the expected
+    // number of bytes.
     uint32_t type_size = concrete_type->byte_size();
-    if (type_size > sizeof(DwarfExprEval::StackEntry)) {
+    if (type_size > sizeof(DwarfStackEntry::UnsignedType)) {
       return cb(
           Err(fxl::StringPrintf("Result size insufficient for type of size %u. "
                                 "Please file a bug with a repro case.",
@@ -43,11 +46,14 @@
     else if (eval.result_is_constant())
       source = ExprValueSource(ExprValueSource::Type::kConstant);
 
-    uint64_t result_int = eval.GetResult();
+    // Assuming little-endian and that the types in the DwarfExprEval are in a union, we can
+    // copy the bytes out using the unsigned value without type checking.
+    DwarfStackEntry result = eval.GetResult();
+    auto result_value = result.unsigned_value();
 
     std::vector<uint8_t> data;
     data.resize(type_size);
-    memcpy(data.data(), &result_int, type_size);
+    memcpy(data.data(), &result_value, type_size);
     cb(ExprValue(type, std::move(data), source));
   } else if (eval.GetResultType() == DwarfExprEval::ResultType::kData) {
     // The DWARF result is a block of data.
@@ -59,8 +65,10 @@
     cb(ExprValue(type, eval.TakeResultData(), ExprValueSource(ExprValueSource::Type::kComposite)));
   } else {
     // The DWARF result is a pointer to the value.
-    uint64_t result_int = eval.GetResult();
-    ResolvePointer(context, result_int, type,
+    DwarfStackEntry result = eval.GetResult();
+    if (!result.TreatAsUnsigned())
+      return cb(Err("DWARF expression produced an unexpected type."));
+    ResolvePointer(context, result.unsigned_value(), type,
                    [cb = std::move(cb)](ErrOrValue value) mutable { cb(std::move(value)); });
   }
 }
diff --git a/src/developer/debug/zxdb/expr/resolve_collection.cc b/src/developer/debug/zxdb/expr/resolve_collection.cc
index ae5acea..723b4a5 100644
--- a/src/developer/debug/zxdb/expr/resolve_collection.cc
+++ b/src/developer/debug/zxdb/expr/resolve_collection.cc
@@ -372,15 +372,21 @@
           cb(err);
         } else {
           // Continue resolution on any remaining inheritance steps.
-          ResolveInheritedPtr(context, static_cast<TargetPointer>(eval.GetResult()), remaining,
-                              std::move(cb));
+          if (eval.GetResultType() == DwarfExprEval::ResultType::kData)
+            return cb(Err("Unexpected result type from DWARF expression."));
+          DwarfStackEntry result = eval.GetResult();
+          if (!result.TreatAsUnsigned())
+            return cb(Err("Unexpected result type from DWARF expression."));
+
+          ResolveInheritedPtr(context, static_cast<TargetPointer>(result.unsigned_value()),
+                              remaining, std::move(cb));
         }
       };
 
       // The expression is evaluated by pushing the derived pointer on the evaluation stack and
       // executing the DWARF expression to get the resulting base class pointer.
       auto async_eval = fxl::MakeRefCounted<AsyncDwarfExprEval>(std::move(on_expr_complete));
-      async_eval->dwarf_eval().Push(derived);
+      async_eval->dwarf_eval().Push(DwarfStackEntry(derived));
       async_eval->Eval(context->GetDataProvider(),
                        first_from->GetSymbolContext(context->GetProcessSymbols()),
                        first_from->location_expression());
diff --git a/src/developer/debug/zxdb/symbols/BUILD.gn b/src/developer/debug/zxdb/symbols/BUILD.gn
index 0d0b8b1..1366547 100644
--- a/src/developer/debug/zxdb/symbols/BUILD.gn
+++ b/src/developer/debug/zxdb/symbols/BUILD.gn
@@ -102,6 +102,8 @@
     "dwarf_expr_eval.cc",
     "dwarf_lang.cc",
     "dwarf_location.cc",
+    "dwarf_stack_entry.cc",
+    "dwarf_stack_entry.h",
     "dwarf_symbol_factory.cc",
     "dwarf_symbol_factory.h",
     "dwarf_tag.cc",
diff --git a/src/developer/debug/zxdb/symbols/base_type.cc b/src/developer/debug/zxdb/symbols/base_type.cc
index c9b62bd..0eada2b 100644
--- a/src/developer/debug/zxdb/symbols/base_type.cc
+++ b/src/developer/debug/zxdb/symbols/base_type.cc
@@ -60,6 +60,11 @@
   return base_type == BaseType::kBaseTypeSigned || base_type == kBaseTypeSignedChar;
 }
 
+// static
+bool BaseType::IsUnsigned(int base_type) {
+  return base_type == BaseType::kBaseTypeUnsigned || base_type == kBaseTypeUnsignedChar;
+}
+
 const std::string& BaseType::GetAssignedName() const {
   const std::string& assigned_name = Type::GetAssignedName();
   if (!assigned_name.empty())
diff --git a/src/developer/debug/zxdb/symbols/base_type.h b/src/developer/debug/zxdb/symbols/base_type.h
index 8bc525f86..a695236 100644
--- a/src/developer/debug/zxdb/symbols/base_type.h
+++ b/src/developer/debug/zxdb/symbols/base_type.h
@@ -40,8 +40,11 @@
   // Converts the int to a base type, optionally including the numeric value.
   static std::string BaseTypeToString(int base_type, bool include_number = false);
 
-  // Returns wiether the given base type is a signed integer.
+  // Returns whether the given base type is an (un)signed integer.
   static bool IsSigned(int base_type);
+  bool IsSigned() const { return IsSigned(base_type()); }
+  static bool IsUnsigned(int base_type);
+  bool IsUnsigned() const { return IsUnsigned(base_type()); }
 
   // Symbol override.
   const std::string& GetAssignedName() const final;
diff --git a/src/developer/debug/zxdb/symbols/dwarf_expr_eval.cc b/src/developer/debug/zxdb/symbols/dwarf_expr_eval.cc
index 0887548..f106618 100644
--- a/src/developer/debug/zxdb/symbols/dwarf_expr_eval.cc
+++ b/src/developer/debug/zxdb/symbols/dwarf_expr_eval.cc
@@ -40,23 +40,213 @@
 constexpr uint8_t DW_OP_GNU_const_index = 0xfc;
 constexpr uint8_t DW_OP_GNU_variable_value = 0xfd;
 
-// For debug print StackEntry values.
-std::string ToString128(uint128_t v) {
+// For debug printing expression integer values, this uses hex for all but the smallest numbers
+// which is what we usually expect for printing address arithmetic.
+std::string ExprIntToString(uint128_t v) {
   if (v > 1024)
     return to_hex_string(v);  // Use hex for very large values (probably addresses).
-  return std::to_string(static_cast<uint64_t>(v));  // Use decimal for small values.
+  return to_string(v);
 }
-std::string ToString128(int128_t v) {
-  if (v < 0)
-    return "-" + ToString128(static_cast<uint128_t>(-v));
-  return std::to_string(static_cast<int64_t>(v));
+std::string ExprIntToString(int128_t v) {
+  if (v > 1024)
+    return to_hex_string(v);  // Use hex for very large values (probably addresses).
+  if (v < -1024)
+    return std::string("-") + to_hex_string(-v);
+  return to_string(v);
 }
 
 // Makes a string expressing adding or subtracting the given constant value.
-std::string MakeAddString(DwarfExprEval::SignedStackEntry val) {
+std::string MakeAddString(DwarfExprEval::SignedType val) {
   if (val < 0)
-    return " - " + ToString128(-val);
-  return " + " + ToString128(val);
+    return " - " + to_string(-val);
+  return " + " + to_string(val);
+}
+
+// These macros declare functions to implement arithmetic operators that will work on typed stack
+// entries as well as generic values. I suspect this could be written using "template templates" but
+// this is mostly boring code used only in this one file and the straightforward #define / custom
+// approach is simpler and more maintainable overall.
+//
+// Before calling these functions, the caller will verify that the types of the two arguments (in
+// the case of binary operators) match.
+//
+// The macros take a validator function which is used for checking for divide-by-zero. In other
+// cases, this validator always returns no error.
+#define DEFINE_BINARY_OPERATOR(fn_name, bin_operator, validator)                                \
+  ErrOr<DwarfStackEntry> fn_name(const DwarfStackEntry& a, const DwarfStackEntry& b) {          \
+    if (Err e = validator(a, b); e.has_error())                                                 \
+      return e;                                                                                 \
+                                                                                                \
+    if (a.TreatAsSigned())                                                                      \
+      return DwarfStackEntry(a.type_ref(), a.signed_value() bin_operator b.signed_value());     \
+    if (a.TreatAsUnsigned())                                                                    \
+      return DwarfStackEntry(a.type_ref(), a.unsigned_value() bin_operator b.unsigned_value()); \
+    if (a.TreatAsFloat())                                                                       \
+      return DwarfStackEntry(a.type_ref(), a.float_value() bin_operator b.float_value());       \
+    if (a.TreatAsDouble())                                                                      \
+      return DwarfStackEntry(a.type_ref(), a.double_value() bin_operator b.double_value());     \
+                                                                                                \
+    FX_NOTREACHED();                                                                            \
+    return Err("Internal type error.");                                                         \
+  }
+
+// Like DEFINE_BINARY_OPERATOR but doesn't support floating point types. Returns an error if the
+// input is floating-point
+#define DEFINE_INTEGRAL_BINARY_OPERATOR(fn_name, bin_operator, validator)                       \
+  ErrOr<DwarfStackEntry> fn_name(const DwarfStackEntry& a, const DwarfStackEntry& b) {          \
+    if (Err e = validator(a, b); e.has_error())                                                 \
+      return e;                                                                                 \
+                                                                                                \
+    if (a.TreatAsSigned())                                                                      \
+      return DwarfStackEntry(a.type_ref(), a.signed_value() bin_operator b.signed_value());     \
+    if (a.TreatAsUnsigned())                                                                    \
+      return DwarfStackEntry(a.type_ref(), a.unsigned_value() bin_operator b.unsigned_value()); \
+                                                                                                \
+    return Err("Requires integral type.");                                                      \
+  }
+
+// Like DEFINE_BINARY_OPERATOR but casts the result to a 0 or 1 generic value to implement
+// comparison operations.
+#define DEFINE_COMPARISON_OPERATOR(fn_name, bin_operator)                              \
+  ErrOr<DwarfStackEntry> fn_name(const DwarfStackEntry& a, const DwarfStackEntry& b) { \
+    bool result = false;                                                               \
+                                                                                       \
+    if (a.TreatAsSigned()) {                                                           \
+      result = a.signed_value() bin_operator b.signed_value();                         \
+    } else if (a.TreatAsUnsigned()) {                                                  \
+      result = a.unsigned_value() bin_operator b.unsigned_value();                     \
+    } else if (a.TreatAsFloat()) {                                                     \
+      result = a.float_value() bin_operator b.float_value();                           \
+    } else if (a.TreatAsDouble()) {                                                    \
+      result = a.double_value() bin_operator b.double_value();                         \
+    } else {                                                                           \
+      FX_NOTREACHED();                                                                 \
+      return Err("Internal type error.");                                              \
+    }                                                                                  \
+                                                                                       \
+    return DwarfStackEntry(result ? 1 : 0);                                            \
+  }
+
+#define DEFINE_INTEGRAL_UNARY_OPERATOR(fn_name, un_operator)                \
+  ErrOr<DwarfStackEntry> fn_name(const DwarfStackEntry& a) {                \
+    if (a.TreatAsSigned())                                                  \
+      return DwarfStackEntry(a.type_ref(), un_operator a.signed_value());   \
+    if (a.TreatAsUnsigned())                                                \
+      return DwarfStackEntry(a.type_ref(), un_operator a.unsigned_value()); \
+                                                                            \
+    FX_NOTREACHED();                                                        \
+    return Err("Internal type error.");                                     \
+  }
+
+Err NoValidator(const DwarfStackEntry&, const DwarfStackEntry& b) { return Err(); }
+
+Err NonzeroDenominatorValidator(const DwarfStackEntry&, const DwarfStackEntry& b) {
+  if (b.IsZero())
+    return Err("Divide by zero.");
+  return Err();
+}
+
+// From the DWARF spec:
+//
+//   Operations other than DW_OP_abs, DW_OP_div, DW_OP_minus, DW_OP_mul, DW_OP_neg and DW_OP_plus
+//   require integral types of the operand (either integral base type or the generic type).
+//
+// We could allow modulo for floating point values also but the spec says not to.
+DEFINE_BINARY_OPERATOR(DwarfOperatorPlus, +, NoValidator)
+DEFINE_BINARY_OPERATOR(DwarfOperatorMinus, -, NoValidator)
+DEFINE_BINARY_OPERATOR(DwarfOperatorTimes, *, NoValidator)
+DEFINE_INTEGRAL_BINARY_OPERATOR(DwarfOperatorMod, %, NonzeroDenominatorValidator)
+
+DEFINE_COMPARISON_OPERATOR(DwarfOperatorEquals, ==)
+DEFINE_COMPARISON_OPERATOR(DwarfOperatorNotEquals, !=)
+DEFINE_COMPARISON_OPERATOR(DwarfOperatorLessEquals, <=)
+DEFINE_COMPARISON_OPERATOR(DwarfOperatorGreaterEquals, >=)
+DEFINE_COMPARISON_OPERATOR(DwarfOperatorLess, <)
+DEFINE_COMPARISON_OPERATOR(DwarfOperatorGreater, >)
+
+DEFINE_INTEGRAL_BINARY_OPERATOR(DwarfOperatorShiftLeft, <<, NoValidator)
+DEFINE_INTEGRAL_BINARY_OPERATOR(DwarfOperatorShiftRight, >>, NoValidator)
+DEFINE_INTEGRAL_BINARY_OPERATOR(DwarfOperatorBitOr, |, NoValidator)
+DEFINE_INTEGRAL_BINARY_OPERATOR(DwarfOperatorBitAnd, &, NoValidator)
+DEFINE_INTEGRAL_BINARY_OPERATOR(DwarfOperatorBitXor, ^, NoValidator)
+
+DEFINE_INTEGRAL_UNARY_OPERATOR(DwarfOperatorBitNot, ~)
+
+// The "DW_OP_div" specifies signed division in the generic type case (actually it specifies signed
+// division for all cases, but if the values are typed unsigned, we do unsigned division because it
+// seems to make more sense). This is implemented using the macro for the common cases, but a
+// custom wrapper implementation that catches the generic case.
+DEFINE_BINARY_OPERATOR(DwarfOperatorDivideTyped, /, NonzeroDenominatorValidator)
+ErrOr<DwarfStackEntry> DwarfOperatorDivide(const DwarfStackEntry& a, const DwarfStackEntry& b) {
+  if (a.is_generic()) {
+    if (Err e = NonzeroDenominatorValidator(a, b); e.has_error())
+      return e;
+    return DwarfStackEntry(static_cast<DwarfStackEntry::SignedType>(a.unsigned_value()) /
+                           static_cast<DwarfStackEntry::SignedType>(b.unsigned_value()));
+  }
+  return DwarfOperatorDivideTyped(a, b);
+}
+
+// Negation requires that "generic" values are casted to signed.
+//
+// There is a question on how to define negation of an explicitly unsigned value. The spec doesn't
+// say anything. This currently defines it as a no-op, but it could also be like the generic case
+// where the bits are reinterpreted. Probably this will not be emitted by a compiler so it won't
+// matter.
+ErrOr<DwarfStackEntry> DwarfOperatorNeg(const DwarfStackEntry& a) {
+  if (a.is_generic())
+    return DwarfStackEntry(static_cast<uint128_t>(-static_cast<int128_t>(a.unsigned_value())));
+
+  if (a.TreatAsSigned())
+    return DwarfStackEntry(a.type_ref(), -a.signed_value());
+  if (a.TreatAsUnsigned())
+    return DwarfStackEntry(a.type_ref(), a.unsigned_value());  // No-op, see above.
+  if (a.TreatAsFloat())
+    return DwarfStackEntry(a.type_ref(), -a.float_value());
+  if (a.TreatAsDouble())
+    return DwarfStackEntry(a.type_ref(), -a.double_value());
+
+  FX_NOTREACHED();
+  return Err("Internal type error");
+}
+
+// This one is an operator to DWARF but not C++. Generic values get treated as signed.
+ErrOr<DwarfStackEntry> DwarfOperatorAbs(const DwarfStackEntry& a) {
+  if (a.is_generic()) {
+    auto as_signed = static_cast<DwarfStackEntry::SignedType>(a.unsigned_value());
+    return as_signed < 0 ? DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-as_signed))
+                         : a;
+  }
+
+  // Explicit types. The DWARF spec doesn't separate out non-generic types so technically the
+  // requirement that the result is "interpreted as signed" applies, but I don't think they meant
+  // that if the value has an explicitly defined unsigned type.
+  if (a.TreatAsSigned()) {
+    if (a.signed_value() < 0)
+      return DwarfStackEntry(a.type_ref(), -a.signed_value());
+    return DwarfStackEntry(a.type_ref(), a.signed_value());
+  }
+  if (a.TreatAsUnsigned())
+    return DwarfStackEntry(a.type_ref(), a.unsigned_value());  // No-op.
+  if (a.TreatAsFloat())
+    return DwarfStackEntry(a.type_ref(), fabsf(a.float_value()));
+  if (a.TreatAsDouble())
+    return DwarfStackEntry(a.type_ref(), fabs(a.double_value()));
+
+  FX_NOTREACHED();
+  return Err("Internal type error");
+}
+
+// This does a sign-extended shift right. When the type is known, we do the same thing as a regular
+// shift right (sign-extended only for signed types). I'm assuming that the DWARF spec's
+// description of reinterpreting as a signed type only applies to generic types.
+ErrOr<DwarfStackEntry> DwarfOperatorShiftRightArithmetically(const DwarfStackEntry& a,
+                                                             const DwarfStackEntry& b) {
+  if (a.is_generic()) {
+    return DwarfStackEntry(static_cast<uint128_t>(static_cast<int128_t>(a.unsigned_value()) >>
+                                                  static_cast<int128_t>(b.unsigned_value())));
+  }
+  return DwarfOperatorShiftRight(a, b);
 }
 
 }  // namespace
@@ -71,7 +261,7 @@
   FX_CHECK(!in_completion_callback_);
 }
 
-void DwarfExprEval::Push(StackEntry value) { stack_.push_back(value); }
+void DwarfExprEval::Push(DwarfStackEntry value) { stack_.push_back(value); }
 
 DwarfExprEval::ResultType DwarfExprEval::GetResultType() const {
   FX_DCHECK(is_complete_);
@@ -82,7 +272,7 @@
   return result_type_;
 }
 
-DwarfExprEval::StackEntry DwarfExprEval::GetResult() const {
+DwarfStackEntry DwarfExprEval::GetResult() const {
   FX_DCHECK(is_complete_);
   FX_DCHECK(is_success_);
   return stack_.back();
@@ -99,7 +289,7 @@
                                               CompletionCallback cb) {
   SetUp(std::move(data_provider), symbol_context, expr, std::move(cb));
 
-  // Note: ContinueEval() may call callback, which may delete |this|
+  // Note: ContinueEval() may call callback, which may delete |this|.
   return ContinueEval() ? Completion::kSync : Completion::kAsync;
 }
 
@@ -197,7 +387,7 @@
       return AppendString("DW_OP_lit" + std::to_string(literal_value),
                           "push(" + std::to_string(literal_value) + ")");
     } else {
-      Push(literal_value);
+      Push(DwarfStackEntry(literal_value));
     }
     return Completion::kSync;
   }
@@ -257,64 +447,49 @@
       // We don't have multiple address spaces.
       return ReportError("DW_OP_xderef opcode is not applicable to this platform.");
     case llvm::dwarf::DW_OP_abs:
-      return OpUnary(
-          [](StackEntry a) { return static_cast<StackEntry>(llabs(static_cast<long long>(a))); },
-          "DW_OP_abs");
+      return OpUnary(&DwarfOperatorAbs, "DW_OP_abs");
     case llvm::dwarf::DW_OP_and:
-      return OpBinary([](StackEntry a, StackEntry b) { return a & b; }, "DW_OP_and");
+      return OpBinary(&DwarfOperatorBitAnd, "DW_OP_and");
     case llvm::dwarf::DW_OP_div:
-      return OpDiv();
+      return OpBinary(&DwarfOperatorDivide, "DW_OP_div");
     case llvm::dwarf::DW_OP_minus:
-      return OpBinary([](StackEntry a, StackEntry b) { return a - b; }, "DW_OP_minus");
+      return OpBinary(&DwarfOperatorMinus, "DW_OP_minus");
     case llvm::dwarf::DW_OP_mod:
-      return OpMod();
+      return OpBinary(&DwarfOperatorMod, "DW_OP_mod");
     case llvm::dwarf::DW_OP_mul:
-      return OpBinary([](StackEntry a, StackEntry b) { return a * b; }, "DW_OP_mul");
+      return OpBinary(&DwarfOperatorTimes, "DW_OP_mul");
     case llvm::dwarf::DW_OP_neg:
-      return OpUnary(
-          [](StackEntry a) { return static_cast<StackEntry>(-static_cast<SignedStackEntry>(a)); },
-          "DW_OP_neg");
+      return OpUnary(&DwarfOperatorNeg, "DW_OP_neg");
     case llvm::dwarf::DW_OP_not:
-      return OpUnary([](StackEntry a) { return ~a; }, "DW_OP_not");
+      return OpUnary(&DwarfOperatorBitNot, "DW_OP_not");
     case llvm::dwarf::DW_OP_or:
-      return OpBinary([](StackEntry a, StackEntry b) { return a | b; }, "DW_OP_or");
+      return OpBinary(&DwarfOperatorBitOr, "DW_OP_or");
     case llvm::dwarf::DW_OP_plus:
-      return OpBinary([](StackEntry a, StackEntry b) { return a + b; }, "DW_OP_plus");
+      return OpBinary(&DwarfOperatorPlus, "DW_OP_plus");
     case llvm::dwarf::DW_OP_plus_uconst:
       return OpPlusUconst();
     case llvm::dwarf::DW_OP_shl:
-      return OpBinary([](StackEntry a, StackEntry b) { return a << b; }, "DW_OP_shl");
+      return OpBinary(&DwarfOperatorShiftLeft, "DW_OP_shl");
     case llvm::dwarf::DW_OP_shr:
-      return OpBinary([](StackEntry a, StackEntry b) { return a >> b; }, "DW_OP_shr");
+      return OpBinary(&DwarfOperatorShiftRight, "DW_OP_shr");
     case llvm::dwarf::DW_OP_shra:
-      return OpBinary(
-          [](StackEntry a, StackEntry b) {
-            return static_cast<StackEntry>(static_cast<SignedStackEntry>(a) >>
-                                           static_cast<SignedStackEntry>(b));
-          },
-          "DW_OP_shra");
+      return OpBinary(&DwarfOperatorShiftRightArithmetically, "DW_OP_shra");
     case llvm::dwarf::DW_OP_xor:
-      return OpBinary([](StackEntry a, StackEntry b) { return a ^ b; }, "DW_OP_xor");
+      return OpBinary(&DwarfOperatorBitXor, "DW_OP_xor");
     case llvm::dwarf::DW_OP_bra:
       return OpBra();
     case llvm::dwarf::DW_OP_eq:
-      return OpBinary([](StackEntry a, StackEntry b) { return static_cast<StackEntry>(a == b); },
-                      "DW_OP_eq");
+      return OpBinary(&DwarfOperatorEquals, "DW_OP_eq");
     case llvm::dwarf::DW_OP_ge:
-      return OpBinary([](StackEntry a, StackEntry b) { return static_cast<StackEntry>(a >= b); },
-                      "DW_OP_ge");
+      return OpBinary(&DwarfOperatorGreaterEquals, "DW_OP_ge");
     case llvm::dwarf::DW_OP_gt:
-      return OpBinary([](StackEntry a, StackEntry b) { return static_cast<StackEntry>(a > b); },
-                      "DW_OP_gt");
+      return OpBinary(&DwarfOperatorGreater, "DW_OP_gt");
     case llvm::dwarf::DW_OP_le:
-      return OpBinary([](StackEntry a, StackEntry b) { return static_cast<StackEntry>(a <= b); },
-                      "DW_OP_le");
+      return OpBinary(&DwarfOperatorLessEquals, "DW_OP_le");
     case llvm::dwarf::DW_OP_lt:
-      return OpBinary([](StackEntry a, StackEntry b) { return static_cast<StackEntry>(a < b); },
-                      "DW_OP_lt");
+      return OpBinary(&DwarfOperatorLess, "DW_OP_lt");
     case llvm::dwarf::DW_OP_ne:
-      return OpBinary([](StackEntry a, StackEntry b) { return static_cast<StackEntry>(a != b); },
-                      "DW_OP_ne");
+      return OpBinary(&DwarfOperatorNotEquals, "DW_OP_ne");
     case llvm::dwarf::DW_OP_skip:
       return OpSkip();
       // DW_OP_lit*, DW_OP_reg*, and DW_OP_breg* are handled at the top of the function.
@@ -412,7 +587,7 @@
 }
 
 DwarfExprEval::Completion DwarfExprEval::PushRegisterWithOffset(int dwarf_register_number,
-                                                                SignedStackEntry offset) {
+                                                                SignedType offset) {
   // Reading register data means the result is not constant.
   result_is_constant_ = false;
 
@@ -434,9 +609,9 @@
       // stack machine for vector computations (it's not specified that the stack items are large
       // enough). When it uses a stack register for a floating-point scalar computation, it just
       // uses the low bits.
-      StackEntry reg_value = 0;
-      memcpy(&reg_value, reg_data->data(), std::min(sizeof(StackEntry), reg_data->size()));
-      Push(reg_value + offset);
+      UnsignedType reg_value = 0;
+      memcpy(&reg_value, reg_data->data(), std::min(sizeof(UnsignedType), reg_data->size()));
+      Push(DwarfStackEntry(reg_value + offset));
 
       // When the current value represents a register, save that fact.
       if (offset == 0)
@@ -457,9 +632,9 @@
         }
 
         // Truncate/convert from little-endian as above.
-        StackEntry reg_value = 0;
-        memcpy(&reg_value, reg_data.data(), std::min(sizeof(StackEntry), reg_data.size()));
-        weak_eval->Push(static_cast<StackEntry>(reg_value + offset));
+        UnsignedType reg_value = 0;
+        memcpy(&reg_value, reg_data.data(), std::min(sizeof(UnsignedType), reg_data.size()));
+        weak_eval->Push(DwarfStackEntry(static_cast<UnsignedType>(reg_value + offset)));
 
         // When the current value represents a register, save that fact.
         if (offset == 0)
@@ -472,7 +647,7 @@
   return Completion::kAsync;
 }
 
-bool DwarfExprEval::ReadSigned(int byte_size, SignedStackEntry* output) {
+bool DwarfExprEval::ReadSigned(int byte_size, SignedType* output) {
   switch (byte_size) {
     case 1:
       if (auto v = data_extractor_.Read<int8_t>()) {
@@ -504,7 +679,7 @@
   return false;
 }
 
-bool DwarfExprEval::ReadUnsigned(int byte_size, StackEntry* output) {
+bool DwarfExprEval::ReadUnsigned(int byte_size, UnsignedType* output) {
   switch (byte_size) {
     case 1:
       if (auto v = data_extractor_.Read<uint8_t>()) {
@@ -536,7 +711,7 @@
   return false;
 }
 
-bool DwarfExprEval::ReadLEBSigned(SignedStackEntry* output) {
+bool DwarfExprEval::ReadLEBSigned(SignedType* output) {
   if (auto result = data_extractor_.ReadSleb128()) {
     *output = *result;
     return true;
@@ -545,7 +720,7 @@
   return false;
 }
 
-bool DwarfExprEval::ReadLEBUnsigned(StackEntry* output) {
+bool DwarfExprEval::ReadLEBUnsigned(UnsignedType* output) {
   if (auto result = data_extractor_.ReadUleb128()) {
     *output = *result;
     return true;
@@ -603,30 +778,52 @@
 
 void DwarfExprEval::ReportStackUnderflow() { ReportError("Stack underflow for DWARF expression."); }
 
-DwarfExprEval::Completion DwarfExprEval::OpUnary(StackEntry (*op)(StackEntry),
-                                                 const char* op_name) {
+DwarfExprEval::Completion DwarfExprEval::OpUnary(
+    ErrOr<DwarfStackEntry> (*op)(const DwarfStackEntry&), const char* op_name) {
   if (is_string_output())
     return AppendString(op_name);
 
-  if (stack_.empty())
+  if (stack_.empty()) {
     ReportStackUnderflow();
-  else
-    stack_.back() = op(stack_.back());
+  } else {
+    ErrOr<DwarfStackEntry> result = op(stack_.back());
+    if (result.ok()) {
+      stack_.back() = std::move(result.value());
+    } else {
+      ReportError("Error evaluating " + std::string(op_name) +
+                  " in DWARF expression: " + result.err().msg());
+    }
+  }
   return Completion::kSync;
 }
 
-DwarfExprEval::Completion DwarfExprEval::OpBinary(StackEntry (*op)(StackEntry, StackEntry),
-                                                  const char* op_name) {
+DwarfExprEval::Completion DwarfExprEval::OpBinary(
+    ErrOr<DwarfStackEntry> (*op)(const DwarfStackEntry&, const DwarfStackEntry&),
+    const char* op_name) {
   if (is_string_output())
     return AppendString(op_name);
 
   if (stack_.size() < 2) {
     ReportStackUnderflow();
   } else {
-    StackEntry b = stack_.back();
+    DwarfStackEntry b = stack_.back();
     stack_.pop_back();
-    StackEntry a = stack_.back();
-    stack_.back() = op(a, b);
+    DwarfStackEntry a = stack_.back();
+    stack_.pop_back();
+
+    if (!a.SameTypeAs(b)) {
+      ReportError(std::string(op_name) + " called on incompatible types " + a.GetTypeDescription() +
+                  " and " + b.GetTypeDescription());
+      return Completion::kSync;
+    }
+
+    ErrOr<DwarfStackEntry> result = op(a, b);
+    if (result.ok()) {
+      stack_.push_back(std::move(result.value()));
+    } else {
+      ReportError("Error evaluating " + std::string(op_name) +
+                  " in DWARF expression: " + result.err().msg());
+    }
   }
   return Completion::kSync;
 }
@@ -650,7 +847,7 @@
                        " but no addr_base is available.");
   }
 
-  StackEntry addr_table_index;
+  UnsignedType addr_table_index;
   if (!ReadLEBUnsigned(&addr_table_index))
     return Completion::kSync;
 
@@ -662,22 +859,22 @@
   result_type_ = result_type;
   if (result_type == ResultType::kPointer) {
     // Addresses need to be relocated according to the module offset.
-    StackEntry new_entry = symbol_context_.RelativeToAbsolute(result_or.value());
+    UnsignedType new_entry = symbol_context_.RelativeToAbsolute(result_or.value());
     if (is_string_output()) {
-      AppendString(std::string(op_name) + "(" + ToString128(addr_table_index) +
+      AppendString(std::string(op_name) + "(" + ExprIntToString(addr_table_index) +
                    ", with addr_base=" + to_hex_string(*base) + ") -> rel=" +
                    to_hex_string(result_or.value()) + ", abs=" + to_hex_string(new_entry));
     } else {
-      Push(new_entry);
+      Push(DwarfStackEntry(new_entry));
     }
   } else {
     // Constants are ready to use.
-    StackEntry new_entry = static_cast<StackEntry>(result_or.value());
+    UnsignedType new_entry = static_cast<UnsignedType>(result_or.value());
     if (is_string_output()) {
-      AppendString(std::string(op_name) + "(" + ToString128(addr_table_index) +
+      AppendString(std::string(op_name) + "(" + ExprIntToString(addr_table_index) +
                    ", with addr_base=" + to_hex_string(*base) + ") -> " + to_hex_string(new_entry));
     } else {
-      Push(new_entry);
+      Push(DwarfStackEntry(new_entry));
     }
   }
 
@@ -687,7 +884,7 @@
 // 1 parameter: unsigned the size of a pointer. This is relative to the load address of the current
 // module. It is used to for globals and statics.
 DwarfExprEval::Completion DwarfExprEval::OpAddr() {
-  StackEntry offset;
+  UnsignedType offset;
   if (!ReadUnsigned(kTargetPointerSize, &offset))
     return Completion::kSync;
 
@@ -701,22 +898,23 @@
     return AppendString("push(" + to_hex_string(address) + ")");
   }
 
-  Push(address);
+  Push(DwarfStackEntry(address));
   return Completion::kSync;
 }
 
 // ULEB128 size + ULEB128 offset.
 DwarfExprEval::Completion DwarfExprEval::OpBitPiece() {
-  StackEntry size;
+  UnsignedType size;
   if (!ReadLEBUnsigned(&size))
     return Completion::kSync;
 
-  StackEntry offset;
+  UnsignedType offset;
   if (!ReadLEBUnsigned(&offset))
     return Completion::kSync;
 
   if (is_string_output())
-    return AppendString("DW_OP_bit_piece(" + ToString128(size) + ", " + ToString128(offset) + ")");
+    return AppendString("DW_OP_bit_piece(" + ExprIntToString(size) + ", " +
+                        ExprIntToString(offset) + ")");
 
   // Clang will generate bit_piece operations to make 80-bit long double constants, but the
   // expressions are invalid: https://bugs.llvm.org/show_bug.cgi?id=43682
@@ -735,12 +933,12 @@
 DwarfExprEval::Completion DwarfExprEval::OpBra() {
   // "The 2-byte constant is the number of bytes of the DWARF expression to skip forward or backward
   // from the current operation, beginning after the 2-byte constant."
-  SignedStackEntry skip_amount = 0;
+  SignedType skip_amount = 0;
   if (!ReadSigned(2, &skip_amount))
     return Completion::kSync;
 
   if (is_string_output())
-    return AppendString("DW_OP_bra(" + ToString128(skip_amount) + ")");
+    return AppendString("DW_OP_bra(" + ExprIntToString(skip_amount) + ")");
 
   if (stack_.empty()) {
     ReportStackUnderflow();
@@ -748,9 +946,9 @@
   }
 
   // 0 @ top of stack means don't take the branch.
-  StackEntry condition = stack_.back();
+  DwarfStackEntry condition = stack_.back();
   stack_.pop_back();
-  if (condition == 0)
+  if (condition.IsZero())
     return Completion::kSync;
 
   // Otherwise take the branch.
@@ -762,13 +960,14 @@
 DwarfExprEval::Completion DwarfExprEval::OpBreg(uint8_t op) {
   int reg_index = op - llvm::dwarf::DW_OP_breg0;
 
-  SignedStackEntry offset = 0;
+  SignedType offset = 0;
   if (!ReadLEBSigned(&offset))
     return Completion::kSync;
 
   if (is_string_output()) {
-    return AppendString("DW_OP_breg" + std::to_string(reg_index) + "(" + ToString128(offset) + ")",
-                        GetRegisterName(reg_index) + MakeAddString(offset));
+    return AppendString(
+        "DW_OP_breg" + std::to_string(reg_index) + "(" + ExprIntToString(offset) + ")",
+        GetRegisterName(reg_index) + MakeAddString(offset));
   }
 
   result_type_ = ResultType::kPointer;
@@ -782,34 +981,13 @@
   // Reading the CFA means the result is not constant.
   result_is_constant_ = false;
 
-  if (StackEntry cfa = data_provider_->GetCanonicalFrameAddress())
-    Push(cfa);
+  if (UnsignedType cfa = data_provider_->GetCanonicalFrameAddress())
+    Push(DwarfStackEntry(cfa));
   else
     ReportError("Frame address is 0.");
   return Completion::kSync;
 }
 
-DwarfExprEval::Completion DwarfExprEval::OpDiv() {
-  if (is_string_output())
-    return AppendString("DW_OP_div");
-
-  if (stack_.size() < 2) {
-    ReportStackUnderflow();
-  } else {
-    StackEntry b = stack_.back();
-    stack_.pop_back();
-    StackEntry a = stack_.back();
-
-    if (b == 0) {
-      ReportError("DWARF expression divided by zero.");
-    } else {
-      stack_.back() = static_cast<StackEntry>(static_cast<SignedStackEntry>(a) /
-                                              static_cast<SignedStackEntry>(b));
-    }
-  }
-  return Completion::kSync;
-}
-
 DwarfExprEval::Completion DwarfExprEval::OpDrop() {
   if (is_string_output())
     return AppendString("DW_OP_drop");
@@ -836,7 +1014,7 @@
   // A ULEB128 length followed by a sub-expression of that length. This sub-expression is evaluated
   // in a separate stack using the register values that were present at the beginning of the
   // function.
-  StackEntry length;
+  UnsignedType length;
   if (!ReadLEBUnsigned(&length))
     return Completion::kSync;
   if (length == 0 || !data_extractor_.CanRead(static_cast<size_t>(length)))
@@ -889,7 +1067,7 @@
           ReportError("DWARF entry value expression produced an incorrect result type.");
         } else {
           // Success case, save the result.
-          Push(nested->GetResult());
+          Push(DwarfStackEntry(nested->GetResult()));
         }
 
         if (*is_async_completion) {
@@ -914,12 +1092,12 @@
   // Reading the frame base means the result is not constant.
   result_is_constant_ = false;
 
-  SignedStackEntry offset = 0;
+  SignedType offset = 0;
   if (!ReadLEBSigned(&offset))
     return Completion::kSync;
 
   if (is_string_output()) {
-    return AppendString("DW_OP_fbreg(" + ToString128(offset) + ")",
+    return AppendString("DW_OP_fbreg(" + ExprIntToString(offset) + ")",
                         "frame_base" + MakeAddString(offset));
   }
 
@@ -932,13 +1110,13 @@
       return ReportError("Base Pointer is 0, can't evaluate.");
 
     result_type_ = ResultType::kPointer;
-    Push(*bp + offset);
+    Push(DwarfStackEntry(*bp + offset));
     return Completion::kSync;
   }
 
   // Must request async.
   data_provider_->GetFrameBaseAsync(
-      [weak_eval = weak_factory_.GetWeakPtr(), offset](const Err& err, StackEntry value) {
+      [weak_eval = weak_factory_.GetWeakPtr(), offset](const Err& err, UnsignedType value) {
         if (!weak_eval)
           return;
         if (err.has_error()) {
@@ -952,7 +1130,7 @@
         }
 
         weak_eval->result_type_ = ResultType::kPointer;
-        weak_eval->Push(static_cast<StackEntry>(value + offset));
+        weak_eval->Push(DwarfStackEntry(static_cast<UnsignedType>(value + offset)));
 
         // Picks up processing at the next instruction.
         weak_eval->ContinueEval();
@@ -965,17 +1143,17 @@
 DwarfExprEval::Completion DwarfExprEval::OpImplicitPointer(const char* op_name) {
   // GCC generates this when a pointer has been optimized out, but it still can provide the value of
   // the thing that it pointed to. We don't implement this.
-  StackEntry die_offset;
+  UnsignedType die_offset;
   if (!ReadUnsigned(8, &die_offset))
     return Completion::kSync;
 
-  SignedStackEntry value_offset;
+  SignedType value_offset;
   if (!ReadLEBSigned(&value_offset))
     return Completion::kSync;
 
   if (is_string_output()) {
     return AppendString(std::string(op_name) + "(" + to_hex_string(die_offset) + ", " +
-                        ToString128(value_offset) + ")");
+                        ExprIntToString(value_offset) + ")");
   }
 
   return ReportError("Optimized out (DW_OP_implicit_pointer)");
@@ -983,36 +1161,36 @@
 
 // 2 parameters: ULEB128 length, followed by that much data (in machine-endianness).
 DwarfExprEval::Completion DwarfExprEval::OpImplicitValue() {
-  StackEntry len = 0;
+  UnsignedType len = 0;
   if (!ReadLEBUnsigned(&len))
     return Completion::kSync;
-  if (len > sizeof(StackEntry))
+  if (len > sizeof(UnsignedType))
     return ReportError(fxl::StringPrintf("DWARF implicit value length too long: 0x%x.",
                                          static_cast<unsigned>(len)));
 
-  StackEntry value = 0;
+  UnsignedType value = 0;
   if (!data_extractor_.ReadBytes(static_cast<size_t>(len), &value))
     return ReportError("Not enough data for DWARF implicit value.");
 
   if (is_string_output()) {
     return AppendString(
-        "DW_OP_implicit_value(" + ToString128(len) + ", " + to_hex_string(value) + ")",
+        "DW_OP_implicit_value(" + ExprIntToString(len) + ", " + to_hex_string(value) + ")",
         "push(" + to_hex_string(value) + ")");
   }
 
-  Push(value);
+  Push(DwarfStackEntry(value));
   result_type_ = ResultType::kValue;
   return Completion::kSync;
 }
 
 // 1 parameter: ULEB128 constant indexing the register.
 DwarfExprEval::Completion DwarfExprEval::OpRegx() {
-  StackEntry reg = 0;
+  UnsignedType reg = 0;
   if (!ReadLEBUnsigned(&reg))
     return Completion::kSync;
 
   if (is_string_output()) {
-    return AppendString("DW_OP_regx(" + ToString128(reg) + ")",
+    return AppendString("DW_OP_regx(" + ExprIntToString(reg) + ")",
                         GetRegisterName(static_cast<int>(reg)));
   }
 
@@ -1022,17 +1200,17 @@
 
 // 2 parameters: ULEB128 register number + SLEB128 offset.
 DwarfExprEval::Completion DwarfExprEval::OpBregx() {
-  StackEntry reg_val = 0;
+  UnsignedType reg_val = 0;
   if (!ReadLEBUnsigned(&reg_val))
     return Completion::kSync;
   int reg = static_cast<int>(reg_val);
 
-  SignedStackEntry offset = 0;
+  SignedType offset = 0;
   if (!ReadLEBSigned(&offset))
     return Completion::kSync;
 
   if (is_string_output()) {
-    return AppendString("DW_OP_bregx(" + std::to_string(reg) + ", " + ToString128(offset) + ")",
+    return AppendString("DW_OP_bregx(" + std::to_string(reg) + ", " + ExprIntToString(offset) + ")",
                         GetRegisterName(reg) + MakeAddString(offset));
   }
 
@@ -1054,25 +1232,28 @@
     return Completion::kSync;
   }
 
-  if (byte_size == 0 || byte_size > sizeof(StackEntry))
+  if (byte_size == 0 || byte_size > sizeof(UnsignedType))
     return ReportError(fxl::StringPrintf("Invalid DWARF expression read size: %u", byte_size));
 
-  StackEntry addr = stack_.back();
+  DwarfStackEntry addr = stack_.back();
   stack_.pop_back();
-  ReadMemory(addr, byte_size, [](DwarfExprEval* eval, std::vector<uint8_t> data) {
+  if (!addr.TreatAsUnsigned())
+    return ReportError("DW_OP_deref trying to dereference a non-unsigned value.");
+
+  ReadMemory(addr.unsigned_value(), byte_size, [](DwarfExprEval* eval, std::vector<uint8_t> data) {
     // Success. This assumes little-endian and copies starting from the low bytes. The data will
-    // have already been validated to be the correct size so we know it will fit in a StackEntry.
-    FX_DCHECK(data.size() <= sizeof(StackEntry));
-    StackEntry to_push = 0;
+    // have already been validated to be the correct size so we know it will fit in a UnsignedType.
+    FX_DCHECK(data.size() <= sizeof(UnsignedType));
+    UnsignedType to_push = 0;
     memcpy(&to_push, data.data(), data.size());
-    eval->Push(to_push);
+    eval->Push(DwarfStackEntry(to_push));
   });
   return Completion::kAsync;
 }
 
 DwarfExprEval::Completion DwarfExprEval::OpDerefSize() {
   // The operand is a 1-byte unsigned constant following the opcode.
-  StackEntry byte_size = 0;
+  UnsignedType byte_size = 0;
   if (!ReadUnsigned(1, &byte_size))
     return Completion::kSync;
 
@@ -1080,27 +1261,6 @@
   return OpDeref(static_cast<uint32_t>(byte_size), "DW_OP_deref_size", true);
 }
 
-DwarfExprEval::Completion DwarfExprEval::OpMod() {
-  if (is_string_output())
-    return AppendString("DW_OP_mod");
-
-  if (stack_.size() < 2) {
-    ReportStackUnderflow();
-  } else {
-    StackEntry b = stack_.back();
-    stack_.pop_back();
-    StackEntry a = stack_.back();
-
-    if (b == 0) {
-      ReportError("DWARF expression divided by zero.");
-    } else {
-      stack_.back() = static_cast<StackEntry>(static_cast<SignedStackEntry>(a) %
-                                              static_cast<SignedStackEntry>(b));
-    }
-  }
-  return Completion::kSync;
-}
-
 DwarfExprEval::Completion DwarfExprEval::OpOver() {
   if (is_string_output())
     return AppendString("DW_OP_over");
@@ -1115,12 +1275,12 @@
 
 // 1 parameter: 1-byte stack index from the top to push.
 DwarfExprEval::Completion DwarfExprEval::OpPick() {
-  StackEntry index = 0;
+  UnsignedType index = 0;
   if (!ReadUnsigned(1, &index))
     return Completion::kSync;
 
   if (is_string_output())
-    return AppendString("DW_OP_pick(" + ToString128(index) + ")");
+    return AppendString("DW_OP_pick(" + ExprIntToString(index) + ")");
 
   if (stack_.size() <= index) {
     ReportStackUnderflow();
@@ -1134,12 +1294,12 @@
 
 // 1 paramter: ULEB size of item in bytes.
 DwarfExprEval::Completion DwarfExprEval::OpPiece() {
-  StackEntry byte_size = 0;
+  UnsignedType byte_size = 0;
   if (!ReadLEBUnsigned(&byte_size))
     return Completion::kSync;
 
   // Upper-bound sanity check on the piece size to guard against corrupted or malicious data.
-  constexpr StackEntry kMaxPieceSize = 16384;
+  constexpr UnsignedType kMaxPieceSize = 16384;
   if (byte_size > kMaxPieceSize) {
     return ReportError(
         fxl::StringPrintf("DWARF expression listed a data size of %d which is too large.",
@@ -1147,7 +1307,7 @@
   }
 
   if (is_string_output())
-    return AppendString("DW_OP_piece(" + ToString128(byte_size) + ")");
+    return AppendString("DW_OP_piece(" + ExprIntToString(byte_size) + ")");
 
   if (stack_.empty()) {
     // Defining a piece with no previous data on the stack means to write that many bytes that
@@ -1156,20 +1316,25 @@
     return Completion::kSync;
   }
 
-  StackEntry source = stack_.back();
+  DwarfStackEntry source = stack_.back();
   stack_.pop_back();
 
   if (result_type_ == ResultType::kValue) {
     // Simple case where the source of the "piece" is the value at the top of the stack.
-    if (byte_size > sizeof(StackEntry)) {
+    if (byte_size > sizeof(UnsignedType)) {
       return ReportError(
           fxl::StringPrintf("DWARF expression listed a data size of %d which is too large.",
                             static_cast<int>(byte_size)));
     }
 
+    size_t source_valid_bytes = source.GetByteSize();
+    if (byte_size > source_valid_bytes)
+      return ReportError("DW_OP_piece attempting to read more bytes than are valid.");
+
     // We want the low bytes, this assumes little-endian.
-    uint8_t source_as_bytes[sizeof(StackEntry)];
-    memcpy(&source_as_bytes, &source, sizeof(StackEntry));
+    UnsignedType source_value = source.unsigned_value();
+    uint8_t source_as_bytes[sizeof(UnsignedType)];
+    memcpy(&source_as_bytes, &source_value, sizeof(UnsignedType));
     result_data_.Append(std::begin(source_as_bytes), &source_as_bytes[byte_size]);
 
     // Reset the expression state to start a new one.
@@ -1179,90 +1344,105 @@
 
   // This is the more complex case where the top of the stack is a pointer to the value in memory.
   // We read that many bytes from memory and add it to the result data.
-  ReadMemory(source, byte_size, [](DwarfExprEval* eval, std::vector<uint8_t> data) {
-    // Success. Copy to the result.
-    eval->result_data_.Append(data);
+  if (!source.TreatAsUnsigned())
+    return ReportError("DW_OP_piece attempting to dereference invalid type.");
+  ReadMemory(source.unsigned_value(), byte_size,
+             [](DwarfExprEval* eval, std::vector<uint8_t> data) {
+               // Success. Copy to the result.
+               eval->result_data_.Append(data);
 
-    // Reset the expression state to start a new one.
-    eval->result_type_ = ResultType::kPointer;
-  });
+               // Reset the expression state to start a new one.
+               eval->result_type_ = ResultType::kPointer;
+             });
 
   // The ReadMemory call will complete asynchronously.
   return Completion::kAsync;
 }
 
 DwarfExprEval::Completion DwarfExprEval::OpPlusUconst() {
-  // "Pops the top stack entry, adds it to the unsigned LEB128 constant operand and pushes the
+  // "Pops the top stack entry, adds it to the unsigned LEB128 constant operand [...] and pushes the
   // result."
-  StackEntry param = 0;
+  UnsignedType param = 0;
   if (!ReadLEBUnsigned(&param))
     return Completion::kSync;
 
   if (is_string_output()) {
-    return AppendString("DW_OP_plus_uconst(" + ToString128(param) + ")", "+ " + ToString128(param));
+    return AppendString("DW_OP_plus_uconst(" + ExprIntToString(param) + ")",
+                        "+ " + ExprIntToString(param));
   }
 
   if (stack_.empty()) {
     ReportStackUnderflow();
   } else {
-    StackEntry top = stack_.back();
+    DwarfStackEntry top = stack_.back();
     stack_.pop_back();
-    Push(top + param);
+
+    // "interpreted as the same type as the operand popped from the top of the stack."
+    if (top.TreatAsUnsigned()) {
+      Push(DwarfStackEntry(top.type_ref(), top.unsigned_value() + param));
+    } else if (top.TreatAsSigned()) {
+      Push(DwarfStackEntry(top.type_ref(), top.signed_value() + static_cast<SignedType>(param)));
+    } else if (top.TreatAsFloat()) {
+      Push(DwarfStackEntry(top.type_ref(), top.float_value() + static_cast<float>(param)));
+    } else if (top.TreatAsDouble()) {
+      Push(DwarfStackEntry(top.type_ref(), top.double_value() + static_cast<double>(param)));
+    }
   }
   return Completion::kSync;
 }
 
 DwarfExprEval::Completion DwarfExprEval::OpPushSigned(int byte_count, const char* op_name) {
-  SignedStackEntry value = 0;
+  SignedType value = 0;
   if (!ReadSigned(byte_count, &value))
     return Completion::kSync;
 
   if (is_string_output()) {
-    return AppendString(std::string(op_name) + "(" + ToString128(value) + ")",
-                        "push(" + ToString128(value) + ")");
+    return AppendString(std::string(op_name) + "(" + ExprIntToString(value) + ")",
+                        "push(" + ExprIntToString(value) + ")");
   }
 
-  Push(static_cast<StackEntry>(value));
+  Push(DwarfStackEntry(static_cast<UnsignedType>(value)));
   return Completion::kSync;
 }
 
 DwarfExprEval::Completion DwarfExprEval::OpPushUnsigned(int byte_count, const char* op_name) {
-  StackEntry value = 0;
+  UnsignedType value = 0;
   if (!ReadUnsigned(byte_count, &value))
     return Completion::kSync;
 
   if (is_string_output()) {
-    return AppendString(std::string(op_name) + "(" + ToString128(value) + ")",
-                        "push(" + ToString128(value) + ")");
+    return AppendString(std::string(op_name) + "(" + ExprIntToString(value) + ")",
+                        "push(" + ExprIntToString(value) + ")");
   }
 
-  Push(value);
+  Push(DwarfStackEntry(value));
   return Completion::kSync;
 }
 
 DwarfExprEval::Completion DwarfExprEval::OpPushLEBSigned() {
-  SignedStackEntry value = 0;
+  SignedType value = 0;
   if (!ReadLEBSigned(&value))
     return Completion::kSync;
 
   if (is_string_output())
-    return AppendString("DW_OP_consts(" + ToString128(value) + ")",
-                        "push(" + ToString128(value) + ")");
+    return AppendString("DW_OP_consts(" + ExprIntToString(value) + ")",
+                        "push(" + ExprIntToString(value) + ")");
 
-  Push(static_cast<StackEntry>(value));
+  Push(DwarfStackEntry(static_cast<UnsignedType>(value)));
   return Completion::kSync;
 }
 
 DwarfExprEval::Completion DwarfExprEval::OpPushLEBUnsigned() {
-  StackEntry value = 0;
+  UnsignedType value = 0;
   if (!ReadLEBUnsigned(&value))
     return Completion::kSync;
 
-  if (is_string_output())
-    return AppendString("DW_OP_constu(" + ToString128(value) + ")",
-                        "push(" + ToString128(value) + ")");
+  if (is_string_output()) {
+    return AppendString("DW_OP_constu(" + ExprIntToString(value) + ")",
+                        "push(" + ExprIntToString(value) + ")");
+  }
 
-  Push(value);
+  Push(DwarfStackEntry(value));
   return Completion::kSync;
 }
 
@@ -1276,25 +1456,25 @@
   if (stack_.size() < 3) {
     ReportStackUnderflow();
   } else {
-    StackEntry top = stack_[stack_.size() - 1];
-    StackEntry one_back = stack_[stack_.size() - 2];
-    StackEntry two_back = stack_[stack_.size() - 3];
+    DwarfStackEntry top = stack_[stack_.size() - 1];
+    DwarfStackEntry one_back = stack_[stack_.size() - 2];
+    DwarfStackEntry two_back = stack_[stack_.size() - 3];
 
-    stack_[stack_.size() - 1] = one_back;
-    stack_[stack_.size() - 2] = two_back;
-    stack_[stack_.size() - 3] = top;
+    stack_[stack_.size() - 1] = std::move(one_back);
+    stack_[stack_.size() - 2] = std::move(two_back);
+    stack_[stack_.size() - 3] = std::move(top);
   }
   return Completion::kSync;
 }
 
 // 1 parameter: 2-byte signed constant.
 DwarfExprEval::Completion DwarfExprEval::OpSkip() {
-  SignedStackEntry skip_amount = 0;
+  SignedType skip_amount = 0;
   if (!ReadSigned(2, &skip_amount))
     return Completion::kSync;
 
   if (is_string_output()) {
-    return AppendString("DW_OP_skip(" + ToString128(skip_amount) + ")");
+    return AppendString("DW_OP_skip(" + ExprIntToString(skip_amount) + ")");
 
     // Don't actually execute the skip in printing mode, because it could skip backwards to do a
     // loop and we would keep printing from there.
@@ -1335,13 +1515,19 @@
     return Completion::kSync;
   }
 
+  DwarfStackEntry entry = stack_.back();
+  stack_.pop_back();
+  if (!entry.TreatAsUnsigned())
+    return ReportError("Non-unsigned type parameter on stack when computing TLS.");
+  UnsignedType tls_offset = entry.unsigned_value();
+
   auto debug_address = data_provider_->GetDebugAddressForContext(symbol_context_);
 
   if (!debug_address)
     return ReportError("Debug address unavailable.");
 
   data_provider_->GetTLSSegment(
-      symbol_context_, [weak_eval = weak_factory_.GetWeakPtr()](ErrOr<uint64_t> value) {
+      symbol_context_, [tls_offset, weak_eval = weak_factory_.GetWeakPtr()](ErrOr<uint64_t> value) {
         if (!weak_eval) {
           return;
         }
@@ -1351,15 +1537,15 @@
           return;
         }
 
-        weak_eval->stack_.back() += static_cast<StackEntry>(value.value());
+        weak_eval->Push(DwarfStackEntry(static_cast<UnsignedType>(value.value()) + tls_offset));
         weak_eval->ContinueEval();
       });
 
   return Completion::kAsync;
 }
 
-void DwarfExprEval::Skip(SignedStackEntry amount) {
-  SignedStackEntry new_index = static_cast<SignedStackEntry>(data_extractor_.cur()) + amount;
+void DwarfExprEval::Skip(SignedType amount) {
+  SignedType new_index = static_cast<SignedType>(data_extractor_.cur()) + amount;
   if (new_index < 0) {
     // Skip before beginning is an error.
     ReportError("DWARF expression skips out-of-bounds.");
diff --git a/src/developer/debug/zxdb/symbols/dwarf_expr_eval.h b/src/developer/debug/zxdb/symbols/dwarf_expr_eval.h
index c1535f7b..76db1c4 100644
--- a/src/developer/debug/zxdb/symbols/dwarf_expr_eval.h
+++ b/src/developer/debug/zxdb/symbols/dwarf_expr_eval.h
@@ -14,10 +14,12 @@
 #include "src/developer/debug/shared/register_id.h"
 #include "src/developer/debug/zxdb/common/data_extractor.h"
 #include "src/developer/debug/zxdb/common/err.h"
+#include "src/developer/debug/zxdb/common/err_or.h"
 #include "src/developer/debug/zxdb/common/int128_t.h"
 #include "src/developer/debug/zxdb/common/tagged_data_builder.h"
 #include "src/developer/debug/zxdb/symbols/arch.h"
 #include "src/developer/debug/zxdb/symbols/dwarf_expr.h"
+#include "src/developer/debug/zxdb/symbols/dwarf_stack_entry.h"
 #include "src/developer/debug/zxdb/symbols/symbol_context.h"
 #include "src/lib/fxl/macros.h"
 #include "src/lib/fxl/memory/ref_ptr.h"
@@ -72,16 +74,8 @@
     kPretty,   // Decodes values and register names.
   };
 
-  // The DWARF spec says the stack entry "can represent a value of any supported base type of the
-  // target machine". We need to support x87 long doubles (80 bits) and XMM registers (128 bits).
-  // Generally the XMM registers used for floating point use only the low 64 bits and long doubles
-  // are very uncommon, but using 128 bits here covers the edge cases better. The ARM "v" registers
-  // (128 bits) are similar.
-  //
-  // The YMM (256 bit) and ZMM (512 bit) x64 reigisters aren't currently representable in DWARF
-  // expressions so larger numbers are unnecessary.
-  using StackEntry = uint128_t;
-  using SignedStackEntry = int128_t;
+  using SignedType = DwarfStackEntry::SignedType;
+  using UnsignedType = DwarfStackEntry::UnsignedType;
 
   using CompletionCallback = fit::callback<void(DwarfExprEval* eval, const Err& err)>;
 
@@ -89,8 +83,8 @@
   ~DwarfExprEval();
 
   // Pushes a value on the stack. Call before Eval() for the cases where an expression requires
-  // some intitial state.
-  void Push(StackEntry value);
+  // some initial state.
+  void Push(DwarfStackEntry value);
 
   // Clears any existing values in the stack.
   void Clear() { stack_.clear(); }
@@ -106,7 +100,7 @@
   // Valid when is_success() and type() == kPointer/kValue. Returns the result of evaluating the
   // expression. The meaning will be dependent on the context of the expression being evaluated.
   // Most results will be smaller than this in which case they will use only the low bits.
-  StackEntry GetResult() const;
+  DwarfStackEntry GetResult() const;
 
   // Destructively returns the generated data buffer. Valid when is_success() and type() == kData.
   TaggedData TakeResultData();
@@ -170,14 +164,14 @@
   //
   // They return true if the value was read, false if there wasn't enough data (they will issue the
   // error internally, the calling code should just return on failure).
-  bool ReadSigned(int byte_size, SignedStackEntry* output);
-  bool ReadUnsigned(int byte_size, StackEntry* output);
+  bool ReadSigned(int byte_size, SignedType* output);
+  bool ReadUnsigned(int byte_size, UnsignedType* output);
 
   // Reads a signed or unsigned LEB constant from the stream. They return true if the value was
   // read, false if there wasn't enough data (they will issue the error internally, the calling code
   // should just return on failure).
-  bool ReadLEBSigned(SignedStackEntry* output);
-  bool ReadLEBUnsigned(StackEntry* output);
+  bool ReadLEBSigned(SignedType* output);
+  bool ReadLEBUnsigned(UnsignedType* output);
 
   // Schedules an asynchronous memory read. If there is any failure, including short reads, this
   // will report it and fail evaluation.
@@ -196,11 +190,12 @@
 
   // Executes the given unary operation with the top stack entry as the parameter and pushes the
   // result.
-  Completion OpUnary(StackEntry (*op)(StackEntry), const char* op_name);
+  Completion OpUnary(ErrOr<DwarfStackEntry> (*op)(const DwarfStackEntry&), const char* op_name);
 
   // Executes the given binary operation by popping the top two stack entries as parameters (the
   // first is the next-to-top, the second is the top) and pushing the result on the stack.
-  Completion OpBinary(StackEntry (*op)(StackEntry, StackEntry), const char* op_name);
+  Completion OpBinary(ErrOr<DwarfStackEntry> (*op)(const DwarfStackEntry&, const DwarfStackEntry&),
+                      const char* op_name);
 
   // Implements DW_OP_addrx and DW_OP_constx (corresponding to the given result types). The type
   // of the result on the stack will be set to the given result type, and kPointer result types
@@ -219,7 +214,6 @@
   Completion OpCFA();
   Completion OpDeref(uint32_t byte_size, const char* op_name, bool string_include_size);
   Completion OpDerefSize();
-  Completion OpDiv();
   Completion OpDrop();
   Completion OpDup();
   Completion OpEntryValue(const char* op_name);
@@ -228,7 +222,6 @@
   Completion OpImplicitValue();
   Completion OpRegx();
   Completion OpBregx();
-  Completion OpMod();
   Completion OpOver();
   Completion OpPick();
   Completion OpPiece();
@@ -298,7 +291,7 @@
   // Indicates that the expression is complete and that there is a result value.
   bool is_success_ = false;
 
-  std::vector<StackEntry> stack_;
+  std::vector<DwarfStackEntry> stack_;
 
   // Tracks the result when generating composite descriptions via DW_OP_[bit_]piece. A nonempty
   // contents indicates that the final result is of type "kData" (see result_type_ for more).
diff --git a/src/developer/debug/zxdb/symbols/dwarf_expr_eval_unittest.cc b/src/developer/debug/zxdb/symbols/dwarf_expr_eval_unittest.cc
index d186643..7a9ffe3 100644
--- a/src/developer/debug/zxdb/symbols/dwarf_expr_eval_unittest.cc
+++ b/src/developer/debug/zxdb/symbols/dwarf_expr_eval_unittest.cc
@@ -14,6 +14,7 @@
 #include "src/developer/debug/zxdb/symbols/mock_module_symbols.h"
 #include "src/developer/debug/zxdb/symbols/mock_symbol_data_provider.h"
 #include "src/developer/debug/zxdb/symbols/symbol_test_parent_setter.h"
+#include "src/developer/debug/zxdb/symbols/type_test_support.h"
 #include "src/developer/debug/zxdb/symbols/variable.h"
 #include "src/lib/fxl/memory/weak_ptr.h"
 
@@ -46,16 +47,28 @@
   // The DwarfExprEval used in the computation will be in the completed state so tests can check
   // eval().whatever for additional validation after this call returns.
   void DoEvalTest(const std::vector<uint8_t> data, bool expected_success,
-                  DwarfExprEval::Completion expected_completion, uint128_t expected_result,
+                  DwarfExprEval::Completion expected_completion,
+                  const DwarfStackEntry& expected_result,
                   DwarfExprEval::ResultType expected_result_type, const char* expected_string,
                   const char* expected_message = nullptr);
 
   // Same as the above but takes a DwarfExpr.
   void DoEvalTest(DwarfExpr expr, bool expected_success,
-                  DwarfExprEval::Completion expected_completion, uint128_t expected_result,
+                  DwarfExprEval::Completion expected_completion,
+                  const DwarfStackEntry& expected_result,
                   DwarfExprEval::ResultType expected_result_type, const char* expected_string,
                   const char* expected_message = nullptr);
 
+  // Just does the evaluation part of the eval test. This does not check the stringified version
+  // of the expression, and does not clear any previous state of the evaluator.
+  //
+  // Tests can use this variant if they want to set up some stack entries manually and then run an
+  // expression based on those.
+  void DoEval(DwarfExpr expr, bool expected_success, DwarfExprEval::Completion expected_completion,
+              const DwarfStackEntry& expected_result,
+              DwarfExprEval::ResultType expected_result_type,
+              const char* expected_message = nullptr);
+
  private:
   DwarfExprEval eval_;
   fxl::RefPtr<MockSymbolDataProvider> provider_;
@@ -64,7 +77,7 @@
 
 void DwarfExprEvalTest::DoEvalTest(const std::vector<uint8_t> data, bool expected_success,
                                    DwarfExprEval::Completion expected_completion,
-                                   uint128_t expected_result,
+                                   const DwarfStackEntry& expected_result,
                                    DwarfExprEval::ResultType expected_result_type,
                                    const char* expected_string, const char* expected_message) {
   DoEvalTest(DwarfExpr(data), expected_success, expected_completion, expected_result,
@@ -73,7 +86,7 @@
 
 void DwarfExprEvalTest::DoEvalTest(DwarfExpr expr, bool expected_success,
                                    DwarfExprEval::Completion expected_completion,
-                                   uint128_t expected_result,
+                                   const DwarfStackEntry& expected_result,
                                    DwarfExprEval::ResultType expected_result_type,
                                    const char* expected_string, const char* expected_message) {
   // Check string-ification. Do this first because it won't set up the complete state of the
@@ -83,6 +96,15 @@
   EXPECT_EQ(expected_string, stringified);
 
   eval_.Clear();
+  DoEval(expr, expected_success, expected_completion, expected_result, expected_result_type,
+         expected_message);
+}
+
+void DwarfExprEvalTest::DoEval(DwarfExpr expr, bool expected_success,
+                               DwarfExprEval::Completion expected_completion,
+                               const DwarfStackEntry& expected_result,
+                               DwarfExprEval::ResultType expected_result_type,
+                               const char* expected_message) {
   bool callback_issued = false;
   EXPECT_EQ(expected_completion,
             eval_.Eval(provider(), symbol_context_, expr,
@@ -127,27 +149,27 @@
   const char kNoResults[] = "DWARF expression produced no results.";
 
   // Empty expression.
-  DoEvalTest(DwarfExpr(), false, DwarfExprEval::Completion::kSync, 0,
+  DoEvalTest(DwarfExpr(), false, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
              DwarfExprEval::ResultType::kPointer, "", kNoResults);
   EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());
   EXPECT_TRUE(eval().result_is_constant());
 
   // Nonempty expression that produces no results.
-  DoEvalTest({llvm::dwarf::DW_OP_nop}, false, DwarfExprEval::Completion::kSync, 0,
+  DoEvalTest({llvm::dwarf::DW_OP_nop}, false, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
              DwarfExprEval::ResultType::kPointer, "DW_OP_nop", kNoResults);
 }
 
 TEST_F(DwarfExprEvalTest, MarkValue) {
   // A computation without "stack_value" should report the result type as a pointers.
-  DoEvalTest({llvm::dwarf::DW_OP_lit4}, true, DwarfExprEval::Completion::kSync, 4u,
+  DoEvalTest({llvm::dwarf::DW_OP_lit4}, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(4u),
              DwarfExprEval::ResultType::kPointer, "DW_OP_lit4");
   EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());
   EXPECT_TRUE(eval().result_is_constant());
 
   // "stack value" should mark the result as a stack value.
   DoEvalTest({llvm::dwarf::DW_OP_lit4, llvm::dwarf::DW_OP_stack_value}, true,
-             DwarfExprEval::Completion::kSync, 4u, DwarfExprEval::ResultType::kValue,
-             "DW_OP_lit4, DW_OP_stack_value");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(4u),
+             DwarfExprEval::ResultType::kValue, "DW_OP_lit4, DW_OP_stack_value");
   EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());
   EXPECT_TRUE(eval().result_is_constant());
 }
@@ -187,8 +209,8 @@
   constexpr uint64_t kValue = 0x1234567890123;
   provider()->AddRegisterValue(kDWARFReg0ID, true, kValue);
 
-  DoEvalTest({llvm::dwarf::DW_OP_reg0}, true, DwarfExprEval::Completion::kSync, kValue,
-             DwarfExprEval::ResultType::kValue, "DW_OP_reg0");
+  DoEvalTest({llvm::dwarf::DW_OP_reg0}, true, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(kValue), DwarfExprEval::ResultType::kValue, "DW_OP_reg0");
   EXPECT_EQ(RegisterID::kARMv8_x0, eval().current_register_id());
   EXPECT_FALSE(eval().result_is_constant());
 }
@@ -208,7 +230,7 @@
   expr_data.push_back(llvm::dwarf::DW_OP_regx);
   expr_data.push_back(0b00000001);
 
-  DoEvalTest(expr_data, true, DwarfExprEval::Completion::kSync, kValue,
+  DoEvalTest(expr_data, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(kValue),
              DwarfExprEval::ResultType::kValue, "DW_OP_nop, DW_OP_regx(1)");
   EXPECT_EQ(RegisterID::kARMv8_x1, eval().current_register_id());
   EXPECT_FALSE(eval().result_is_constant());
@@ -219,8 +241,8 @@
   constexpr uint64_t kValue = 0x1234567890123;
   provider()->AddRegisterValue(kDWARFReg0ID, false, kValue);
 
-  DoEvalTest({llvm::dwarf::DW_OP_reg0}, true, DwarfExprEval::Completion::kAsync, kValue,
-             DwarfExprEval::ResultType::kValue, "DW_OP_reg0");
+  DoEvalTest({llvm::dwarf::DW_OP_reg0}, true, DwarfExprEval::Completion::kAsync,
+             DwarfStackEntry(kValue), DwarfExprEval::ResultType::kValue, "DW_OP_reg0");
   EXPECT_EQ(RegisterID::kARMv8_x0, eval().current_register_id());
   EXPECT_FALSE(eval().result_is_constant());
 }
@@ -229,8 +251,8 @@
 TEST_F(DwarfExprEvalTest, SyncInvalidOp) {
   // Make a program that consists only of a user-defined opcode (not supported). Can't use
   // DW_OP_lo_user because that's a GNU TLS extension we know about.
-  DoEvalTest({llvm::dwarf::DW_OP_lo_user + 1}, false, DwarfExprEval::Completion::kSync, 0,
-             DwarfExprEval::ResultType::kValue, "INVALID_OPCODE(0xe1)",
+  DoEvalTest({llvm::dwarf::DW_OP_lo_user + 1}, false, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(0), DwarfExprEval::ResultType::kValue, "INVALID_OPCODE(0xe1)",
              "Invalid opcode 0xe1 in DWARF expression.");
 }
 
@@ -245,21 +267,21 @@
   expr_data.push_back(llvm::dwarf::DW_OP_reg0);
   expr_data.push_back(llvm::dwarf::DW_OP_lo_user + 1);
 
-  DoEvalTest(expr_data, false, DwarfExprEval::Completion::kAsync, 0,
+  DoEvalTest(expr_data, false, DwarfExprEval::Completion::kAsync, DwarfStackEntry(0),
              DwarfExprEval::ResultType::kPointer, "DW_OP_reg0, INVALID_OPCODE(0xe1)",
              "Invalid opcode 0xe1 in DWARF expression.");
 }
 
 // Tests the special opcodes that also encode a 0-31 literal.
 TEST_F(DwarfExprEvalTest, LiteralOp) {
-  DoEvalTest({llvm::dwarf::DW_OP_lit4}, true, DwarfExprEval::Completion::kSync, 4u,
+  DoEvalTest({llvm::dwarf::DW_OP_lit4}, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(4u),
              DwarfExprEval::ResultType::kPointer, "DW_OP_lit4");
 }
 
 // Tests that reading fixed-length constant without enough room fails.
 TEST_F(DwarfExprEvalTest, Const4ReadOffEnd) {
-  DoEvalTest({llvm::dwarf::DW_OP_const4u, 0xf0}, false, DwarfExprEval::Completion::kSync, 0,
-             DwarfExprEval::ResultType::kPointer,
+  DoEvalTest({llvm::dwarf::DW_OP_const4u, 0xf0}, false, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(0), DwarfExprEval::ResultType::kPointer,
              "ERROR: \"Bad number format in DWARF expression.\"",
              "Bad number format in DWARF expression.");
 }
@@ -268,8 +290,8 @@
 TEST_F(DwarfExprEvalTest, ConstReadOffEnd) {
   // Note that LLVM allows LEB numbers to run off the end, and in that case just stops reading data
   // and reports the bits read.
-  DoEvalTest({llvm::dwarf::DW_OP_constu}, false, DwarfExprEval::Completion::kSync, 0,
-             DwarfExprEval::ResultType::kPointer,
+  DoEvalTest({llvm::dwarf::DW_OP_constu}, false, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(0), DwarfExprEval::ResultType::kPointer,
              "ERROR: \"Bad number format in DWARF expression.\"",
              "Bad number format in DWARF expression.");
 }
@@ -277,7 +299,7 @@
 TEST_F(DwarfExprEvalTest, Addr) {
   // This encodes the relative address 0x4000.
   DoEvalTest({llvm::dwarf::DW_OP_addr, 0, 0x40, 0, 0, 0, 0, 0, 0}, true,
-             DwarfExprEval::Completion::kSync, kModuleBase + 0x4000,
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(kModuleBase + 0x4000),
              DwarfExprEval::ResultType::kPointer, "DW_OP_addr(0x4000)");
 }
 
@@ -312,8 +334,8 @@
 
   // The "addrx" expression should read the kAddr value from the .debug_addr table at the location
   // we set up, and then relocate it relative to the module's base address.
-  DoEvalTest(addrx_expr, true, DwarfExprEval::Completion::kSync, kModuleBase + kAddr,
-             DwarfExprEval::ResultType::kPointer,
+  DoEvalTest(addrx_expr, true, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(kModuleBase + kAddr), DwarfExprEval::ResultType::kPointer,
              "DW_OP_addrx(8, with addr_base=0xc) -> rel=0x12345678, abs=0x8a345678");
 
   // Same test with "constx". This is the same except the resulting address is not relocated from
@@ -322,13 +344,13 @@
   // Note: I have not actually seen this operator in use. This expected behavior is based only on my
   // reading of the spec.
   DwarfExpr constx_expr({llvm::dwarf::DW_OP_constx, kOffset}, UncachedLazySymbol::MakeUnsafe(var));
-  DoEvalTest(constx_expr, true, DwarfExprEval::Completion::kSync, kAddr,
+  DoEvalTest(constx_expr, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(kAddr),
              DwarfExprEval::ResultType::kValue,
              "DW_OP_constx(8, with addr_base=0xc) -> 0x12345678");
 
   // Same test with an invalid address offset.
   DwarfExpr invalid_expr({llvm::dwarf::DW_OP_constx, 16}, UncachedLazySymbol::MakeUnsafe(var));
-  DoEvalTest(invalid_expr, false, DwarfExprEval::Completion::kSync, 0,
+  DoEvalTest(invalid_expr, false, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
              DwarfExprEval::ResultType::kPointer,
              "ERROR: \"Unable to read .debug_addr section to evaluate expression.\"");
 }
@@ -339,15 +361,15 @@
 
   // reg0 (=100) + 129 = 229 (synchronous).
   // Note: 129 in SLEB is 0x81, 0x01 (example in DWARF spec).
-  DoEvalTest({llvm::dwarf::DW_OP_breg0, 0x81, 0x01}, true, DwarfExprEval::Completion::kSync, 229u,
-             DwarfExprEval::ResultType::kPointer, "DW_OP_breg0(129)");
+  DoEvalTest({llvm::dwarf::DW_OP_breg0, 0x81, 0x01}, true, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(229u), DwarfExprEval::ResultType::kPointer, "DW_OP_breg0(129)");
   EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());
   EXPECT_FALSE(eval().result_is_constant());
 
   // reg9 (=200) - 127 = 73 (asynchronous).
   // -127 in SLEB is 0x81, 0x7f (example in DWARF spec).
-  DoEvalTest({llvm::dwarf::DW_OP_breg9, 0x81, 0x7f}, true, DwarfExprEval::Completion::kAsync, 73u,
-             DwarfExprEval::ResultType::kPointer, "DW_OP_breg9(-127)");
+  DoEvalTest({llvm::dwarf::DW_OP_breg9, 0x81, 0x7f}, true, DwarfExprEval::Completion::kAsync,
+             DwarfStackEntry(73u), DwarfExprEval::ResultType::kPointer, "DW_OP_breg9(-127)");
   EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());
   EXPECT_FALSE(eval().result_is_constant());
 }
@@ -359,21 +381,21 @@
   // reg0 (=100) + 129 = 229 (synchronous).
   // Note: 129 in SLEB is 0x81, 0x01 (example in DWARF spec).
   DoEvalTest({llvm::dwarf::DW_OP_bregx, 0x00, 0x81, 0x01}, true, DwarfExprEval::Completion::kSync,
-             229u, DwarfExprEval::ResultType::kPointer, "DW_OP_bregx(0, 129)");
+             DwarfStackEntry(229u), DwarfExprEval::ResultType::kPointer, "DW_OP_bregx(0, 129)");
   EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());  // Because there's an offset.
   EXPECT_FALSE(eval().result_is_constant());
 
   // reg9 (=200) - 127 = 73 (asynchronous).
   // -127 in SLEB is 0x81, 0x7f (example in DWARF spec).
   DoEvalTest({llvm::dwarf::DW_OP_bregx, 0x09, 0x81, 0x7f}, true, DwarfExprEval::Completion::kAsync,
-             73u, DwarfExprEval::ResultType::kPointer, "DW_OP_bregx(9, -127)");
+             DwarfStackEntry(73u), DwarfExprEval::ResultType::kPointer, "DW_OP_bregx(9, -127)");
   EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());  // Because there's an offset.
   EXPECT_FALSE(eval().result_is_constant());
 
   // No offset should report the register source.
   // reg0 (=100) + 0 = 100 (synchronous).
-  DoEvalTest({llvm::dwarf::DW_OP_bregx, 0x00, 0x00}, true, DwarfExprEval::Completion::kSync, 100u,
-             DwarfExprEval::ResultType::kPointer, "DW_OP_bregx(0, 0)");
+  DoEvalTest({llvm::dwarf::DW_OP_bregx, 0x00, 0x00}, true, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(100u), DwarfExprEval::ResultType::kPointer, "DW_OP_bregx(0, 0)");
   EXPECT_EQ(RegisterID::kARMv8_x0, eval().current_register_id());
   EXPECT_FALSE(eval().result_is_constant());
 }
@@ -384,62 +406,66 @@
 
   // Most expressions involving the CFA are just the CFA itself (GCC likes
   // to declare the function frame base as being equal to the CFA).
-  DoEvalTest({llvm::dwarf::DW_OP_call_frame_cfa}, true, DwarfExprEval::Completion::kSync, kCFA,
-             DwarfExprEval::ResultType::kPointer, "DW_OP_call_frame_cfa");
+  DoEvalTest({llvm::dwarf::DW_OP_call_frame_cfa}, true, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(kCFA), DwarfExprEval::ResultType::kPointer, "DW_OP_call_frame_cfa");
   EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());
   EXPECT_FALSE(eval().result_is_constant());
 }
 
 TEST_F(DwarfExprEvalTest, Const1s) {
   DoEvalTest({llvm::dwarf::DW_OP_const1s, static_cast<uint8_t>(-3)}, true,
-             DwarfExprEval::Completion::kSync, static_cast<DwarfExprEval::StackEntry>(-3),
+             DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-3)),
              DwarfExprEval::ResultType::kPointer, "DW_OP_const1s(-3)");
   EXPECT_TRUE(eval().result_is_constant());
 }
 
 TEST_F(DwarfExprEvalTest, Const1u) {
-  DoEvalTest({llvm::dwarf::DW_OP_const1u, 0xf0}, true, DwarfExprEval::Completion::kSync, 0xf0,
-             DwarfExprEval::ResultType::kPointer, "DW_OP_const1u(240)");
+  DoEvalTest({llvm::dwarf::DW_OP_const1u, 0xf0}, true, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(0xf0), DwarfExprEval::ResultType::kPointer, "DW_OP_const1u(240)");
   EXPECT_TRUE(eval().result_is_constant());
 }
 
 TEST_F(DwarfExprEvalTest, Const2s) {
   DoEvalTest({llvm::dwarf::DW_OP_const2s, static_cast<uint8_t>(-3), 0xff}, true,
-             DwarfExprEval::Completion::kSync, static_cast<DwarfExprEval::StackEntry>(-3),
+             DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-3)),
              DwarfExprEval::ResultType::kPointer, "DW_OP_const2s(-3)");
 }
 
 TEST_F(DwarfExprEvalTest, Const2u) {
   DoEvalTest({llvm::dwarf::DW_OP_const2u, 0x01, 0xf0}, true, DwarfExprEval::Completion::kSync,
-             0xf001, DwarfExprEval::ResultType::kPointer, "DW_OP_const2u(0xf001)");
+             DwarfStackEntry(0xf001), DwarfExprEval::ResultType::kPointer, "DW_OP_const2u(0xf001)");
   EXPECT_TRUE(eval().result_is_constant());
 }
 
 TEST_F(DwarfExprEvalTest, Const4s) {
   DoEvalTest({llvm::dwarf::DW_OP_const4s, static_cast<uint8_t>(-3), 0xff, 0xff, 0xff}, true,
-             DwarfExprEval::Completion::kSync, static_cast<DwarfExprEval::StackEntry>(-3),
+             DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-3)),
              DwarfExprEval::ResultType::kPointer, "DW_OP_const4s(-3)");
   EXPECT_TRUE(eval().result_is_constant());
 }
 
 TEST_F(DwarfExprEvalTest, Const4u) {
   DoEvalTest({llvm::dwarf::DW_OP_const4u, 0x03, 0x02, 0x01, 0xf0}, true,
-             DwarfExprEval::Completion::kSync, 0xf0010203, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_const4u(0xf0010203)");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(0xf0010203),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_const4u(0xf0010203)");
   EXPECT_TRUE(eval().result_is_constant());
 }
 
 TEST_F(DwarfExprEvalTest, Const8s) {
   DoEvalTest({llvm::dwarf::DW_OP_const8s, static_cast<uint8_t>(-3), 0xff, 0xff, 0xff, 0xff, 0xff,
               0xff, 0xff},
-             true, DwarfExprEval::Completion::kSync, static_cast<DwarfExprEval::StackEntry>(-3),
+             true, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-3)),
              DwarfExprEval::ResultType::kPointer, "DW_OP_const8s(-3)");
   EXPECT_TRUE(eval().result_is_constant());
 }
 
 TEST_F(DwarfExprEvalTest, Const8u) {
   DoEvalTest({llvm::dwarf::DW_OP_const8u, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0xf0}, true,
-             DwarfExprEval::Completion::kSync, 0xf001020304050607u,
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(0xf001020304050607u),
              DwarfExprEval::ResultType::kPointer, "DW_OP_const8u(0xf001020304050607)");
   EXPECT_TRUE(eval().result_is_constant());
 }
@@ -447,8 +473,8 @@
 TEST_F(DwarfExprEvalTest, Consts) {
   // -127 in SLEB is 0x81, 0x7f (example in DWARF spec).
   DoEvalTest({llvm::dwarf::DW_OP_consts, 0x81, 0x7f}, true, DwarfExprEval::Completion::kSync,
-             static_cast<DwarfExprEval::StackEntry>(-127), DwarfExprEval::ResultType::kPointer,
-             "DW_OP_consts(-127)");
+             DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-127)),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_consts(-127)");
   EXPECT_TRUE(eval().result_is_constant());
 }
 
@@ -457,36 +483,55 @@
   // 129 in ULEB is 0x81, 0x01 (example in DWARF spec).
   DoEvalTest(
       {llvm::dwarf::DW_OP_constu, 0x81, 0x01, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_drop},
-      true, DwarfExprEval::Completion::kSync, 129u, DwarfExprEval::ResultType::kPointer,
-      "DW_OP_constu(129), DW_OP_lit0, DW_OP_drop");
+      true, DwarfExprEval::Completion::kSync, DwarfStackEntry(129u),
+      DwarfExprEval::ResultType::kPointer, "DW_OP_constu(129), DW_OP_lit0, DW_OP_drop");
   EXPECT_TRUE(eval().result_is_constant());
 }
 
 // Tests both "dup" and "add".
 TEST_F(DwarfExprEvalTest, DupAdd) {
   DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_dup, llvm::dwarf::DW_OP_plus}, true,
-             DwarfExprEval::Completion::kSync, 16u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit8, DW_OP_dup, DW_OP_plus");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(16u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_dup, DW_OP_plus");
   EXPECT_TRUE(eval().result_is_constant());
+
+  // Adding two different types fails, this is an unsigned integer and a generic type.
+  auto uint_type = MakeUint32Type();
+  eval().Clear();
+  eval().Push(DwarfStackEntry(uint_type, uint128_t(17u)));
+  eval().Push(DwarfStackEntry(10u));
+  DoEval(DwarfExpr({llvm::dwarf::DW_OP_plus}), false, DwarfExprEval::Completion::kSync,
+         DwarfStackEntry(0u), DwarfExprEval::ResultType::kPointer);
 }
 
 TEST_F(DwarfExprEvalTest, Neg) {
   // Negate one should give -1 casted to unsigned.
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_neg}, true,
-             DwarfExprEval::Completion::kSync, static_cast<DwarfExprEval::StackEntry>(-1),
+             DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-1)),
              DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_neg");
   EXPECT_TRUE(eval().result_is_constant());
 
   // Double negate should come back to 1.
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_neg, llvm::dwarf::DW_OP_neg}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit1, DW_OP_neg, DW_OP_neg");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_neg, DW_OP_neg");
   EXPECT_TRUE(eval().result_is_constant());
+
+  // Negating a specifically unsigned value is a no-op. I have no idea if this is what DWARF wants
+  // (the spec isn't very specific). With this test we at least clearly define our behavior and
+  // make sure that if it changes, we do so on purpose.
+  auto uint_type = MakeUint32Type();
+  eval().Clear();
+  eval().Push(DwarfStackEntry(uint_type, uint128_t(17u)));
+  DoEval(DwarfExpr({llvm::dwarf::DW_OP_neg}), true, DwarfExprEval::Completion::kSync,
+         DwarfStackEntry(uint_type, uint128_t(17u)), DwarfExprEval::ResultType::kPointer);
 }
 
 TEST_F(DwarfExprEvalTest, Not) {
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_not}, true,
-             DwarfExprEval::Completion::kSync, ~static_cast<DwarfExprEval::StackEntry>(1),
+             DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(~static_cast<DwarfStackEntry::UnsignedType>(1)),
              DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_not");
   EXPECT_TRUE(eval().result_is_constant());
 }
@@ -494,39 +539,63 @@
 TEST_F(DwarfExprEvalTest, Or) {
   // 8 | 1 = 9.
   DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_or}, true,
-             DwarfExprEval::Completion::kSync, 9u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit8, DW_OP_lit1, DW_OP_or");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(9u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit1, DW_OP_or");
   EXPECT_TRUE(eval().result_is_constant());
+
+  // Typed as a float should fail (can't "or" non-integral types).
+  auto float_type = MakeFloatType();
+  eval().Clear();
+  eval().Push(DwarfStackEntry(float_type, 8.0f));
+  eval().Push(DwarfStackEntry(float_type, 1.0f));
+  DoEval(DwarfExpr({llvm::dwarf::DW_OP_or}), false, DwarfExprEval::Completion::kSync,
+         DwarfStackEntry(0u), DwarfExprEval::ResultType::kPointer);
 }
 
 TEST_F(DwarfExprEvalTest, Mul) {
   // 8 * 9 = 72.
   DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit9, llvm::dwarf::DW_OP_mul}, true,
-             DwarfExprEval::Completion::kSync, 72u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit8, DW_OP_lit9, DW_OP_mul");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(72u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit9, DW_OP_mul");
   EXPECT_TRUE(eval().result_is_constant());
+
+  // Typed as a float.
+  auto float_type = MakeFloatType();
+  eval().Clear();
+  eval().Push(DwarfStackEntry(float_type, -8.712f));
+  eval().Push(DwarfStackEntry(float_type, 67.991f));
+  DoEval(DwarfExpr({llvm::dwarf::DW_OP_mul}), true, DwarfExprEval::Completion::kSync,
+         DwarfStackEntry(float_type, -592.3376f), DwarfExprEval::ResultType::kPointer);
 }
 
 TEST_F(DwarfExprEvalTest, Minus) {
   // 8 - 2 = 6.
   DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_minus}, true,
-             DwarfExprEval::Completion::kSync, 6u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit8, DW_OP_lit2, DW_OP_minus");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(6u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit2, DW_OP_minus");
   EXPECT_TRUE(eval().result_is_constant());
+
+  // Typed as a double.
+  auto double_type = MakeDoubleType();
+  eval().Clear();
+  eval().Push(DwarfStackEntry(double_type, 8.712));
+  eval().Push(DwarfStackEntry(double_type, 67.991));
+  DoEval(DwarfExpr({llvm::dwarf::DW_OP_minus}), true, DwarfExprEval::Completion::kSync,
+         DwarfStackEntry(double_type, -59.279), DwarfExprEval::ResultType::kPointer);
 }
 
 TEST_F(DwarfExprEvalTest, Over) {
   // Stack of (1, 2), this pushes "1" on the top.
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_over}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit1, DW_OP_lit2, DW_OP_over");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit2, DW_OP_over");
   EXPECT_TRUE(eval().result_is_constant());
 
   // Same operation with a drop to check the next-to-top item.
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_over,
               llvm::dwarf::DW_OP_drop},
-             true, DwarfExprEval::Completion::kSync, 2u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit1, DW_OP_lit2, DW_OP_over, DW_OP_drop");
+             true, DwarfExprEval::Completion::kSync, DwarfStackEntry(2u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit2, DW_OP_over, DW_OP_drop");
   EXPECT_TRUE(eval().result_is_constant());
 }
 
@@ -534,21 +603,24 @@
   // Stack of 1, 2, 3. Pick 0 -> 3.
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_lit3,
               llvm::dwarf::DW_OP_pick, 0},
-             true, DwarfExprEval::Completion::kSync, 3u, DwarfExprEval::ResultType::kPointer,
+             true, DwarfExprEval::Completion::kSync, DwarfStackEntry(3u),
+             DwarfExprEval::ResultType::kPointer,
              "DW_OP_lit1, DW_OP_lit2, DW_OP_lit3, DW_OP_pick(0)");
   EXPECT_TRUE(eval().result_is_constant());
 
   // Stack of 1, 2, 3. Pick 2 -> 1.
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_lit3,
               llvm::dwarf::DW_OP_pick, 2},
-             true, DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
+             true, DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer,
              "DW_OP_lit1, DW_OP_lit2, DW_OP_lit3, DW_OP_pick(2)");
   EXPECT_TRUE(eval().result_is_constant());
 
   // Stack of 1, 2, 3. Pick 3 -> error.
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_lit3,
               llvm::dwarf::DW_OP_pick, 3},
-             false, DwarfExprEval::Completion::kSync, 0u, DwarfExprEval::ResultType::kPointer,
+             false, DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
+             DwarfExprEval::ResultType::kPointer,
              "DW_OP_lit1, DW_OP_lit2, DW_OP_lit3, DW_OP_pick(3)",
              "Stack underflow for DWARF expression.");
   EXPECT_TRUE(eval().result_is_constant());
@@ -557,12 +629,12 @@
 TEST_F(DwarfExprEvalTest, Swap) {
   // 1, 2, swap -> 2, 1
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_swap}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit1, DW_OP_lit2, DW_OP_swap");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit2, DW_OP_swap");
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_swap,
               llvm::dwarf::DW_OP_drop},
-             true, DwarfExprEval::Completion::kSync, 2u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit1, DW_OP_lit2, DW_OP_swap, DW_OP_drop");
+             true, DwarfExprEval::Completion::kSync, DwarfStackEntry(2u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit2, DW_OP_swap, DW_OP_drop");
   EXPECT_TRUE(eval().result_is_constant());
 }
 
@@ -571,15 +643,17 @@
   // stack elements).
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_lit3,
               llvm::dwarf::DW_OP_rot},
-             true, DwarfExprEval::Completion::kSync, 2u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit1, DW_OP_lit2, DW_OP_lit3, DW_OP_rot");
+             true, DwarfExprEval::Completion::kSync, DwarfStackEntry(2u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit2, DW_OP_lit3, DW_OP_rot");
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_lit3,
               llvm::dwarf::DW_OP_rot, llvm::dwarf::DW_OP_drop},
-             true, DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
+             true, DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer,
              "DW_OP_lit1, DW_OP_lit2, DW_OP_lit3, DW_OP_rot, DW_OP_drop");
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_lit3,
               llvm::dwarf::DW_OP_rot, llvm::dwarf::DW_OP_drop, llvm::dwarf::DW_OP_drop},
-             true, DwarfExprEval::Completion::kSync, 3u, DwarfExprEval::ResultType::kPointer,
+             true, DwarfExprEval::Completion::kSync, DwarfStackEntry(3u),
+             DwarfExprEval::ResultType::kPointer,
              "DW_OP_lit1, DW_OP_lit2, DW_OP_lit3, DW_OP_rot, DW_OP_drop, DW_OP_drop");
   EXPECT_TRUE(eval().result_is_constant());
 }
@@ -587,21 +661,30 @@
 TEST_F(DwarfExprEvalTest, Abs) {
   // Abs of 1 -> 1.
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_abs}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit1, DW_OP_abs");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_abs");
   EXPECT_TRUE(eval().result_is_constant());
 
   // Abs of -1 -> 1.
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_neg, llvm::dwarf::DW_OP_abs}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit1, DW_OP_neg, DW_OP_abs");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_neg, DW_OP_abs");
+
+  // Absolute value of an explicitly unsigned type is a no-op (this value is picked so it will
+  // behave differently if the code treats it as signed.
+  auto uint_type = MakeUint32Type();
+  DwarfStackEntry stack_val(uint_type, static_cast<DwarfStackEntry::UnsignedType>(-5));
+  eval().Clear();
+  eval().Push(stack_val);
+  DoEval(DwarfExpr({llvm::dwarf::DW_OP_neg}), true, DwarfExprEval::Completion::kSync, stack_val,
+         DwarfExprEval::ResultType::kPointer);
 }
 
 TEST_F(DwarfExprEvalTest, And) {
   // 3 (=0b11) & 5 (=0b101) = 1
   DoEvalTest({llvm::dwarf::DW_OP_lit3, llvm::dwarf::DW_OP_lit5, llvm::dwarf::DW_OP_and}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit3, DW_OP_lit5, DW_OP_and");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit3, DW_OP_lit5, DW_OP_and");
   EXPECT_TRUE(eval().result_is_constant());
 }
 
@@ -609,64 +692,77 @@
   // 8 / -2 = -4.
   DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_neg,
               llvm::dwarf::DW_OP_div},
-             true, DwarfExprEval::Completion::kSync, static_cast<DwarfExprEval::StackEntry>(-4),
+             true, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-4)),
              DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit2, DW_OP_neg, DW_OP_div");
   EXPECT_TRUE(eval().result_is_constant());
 
   // Divide by zero should give an error.
   DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_div}, false,
-             DwarfExprEval::Completion::kSync, 0, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit8, DW_OP_lit0, DW_OP_div", "DWARF expression divided by zero.");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit0, DW_OP_div",
+             "Error evaluating DW_OP_div in DWARF expression: Divide by zero.");
 }
 
 TEST_F(DwarfExprEvalTest, Mod) {
   // 7 % 2 = 1
   DoEvalTest({llvm::dwarf::DW_OP_lit7, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_mod}, true,
-             DwarfExprEval::Completion::kSync, 1, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit7, DW_OP_lit2, DW_OP_mod");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit7, DW_OP_lit2, DW_OP_mod");
   EXPECT_TRUE(eval().result_is_constant());
 
   // Modulo 0 should give an error
   DoEvalTest({llvm::dwarf::DW_OP_lit7, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_mod}, false,
-             DwarfExprEval::Completion::kSync, 0, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit7, DW_OP_lit0, DW_OP_mod", "DWARF expression divided by zero.");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit7, DW_OP_lit0, DW_OP_mod",
+             "Error evaluating DW_OP_mod in DWARF expression: Divide by zero.");
 }
 
 TEST_F(DwarfExprEvalTest, PlusUconst) {
   // 7 + 129 = 136. 129 in ULEB is 0x81, 0x01 (example in DWARF spec).
   DoEvalTest({llvm::dwarf::DW_OP_lit7, llvm::dwarf::DW_OP_plus_uconst, 0x81, 0x01}, true,
-             DwarfExprEval::Completion::kSync, 136u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit7, DW_OP_plus_uconst(129)");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(136u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit7, DW_OP_plus_uconst(129)");
   EXPECT_TRUE(eval().result_is_constant());
+
+  // The value should get reinterpreted as the same type as the top of the stack. This adds 129 to
+  // a floating-point number.
+  auto double_type = MakeDoubleType();
+  eval().Clear();
+  eval().Push(DwarfStackEntry(double_type, 3.14159));
+  DoEval(DwarfExpr({llvm::dwarf::DW_OP_plus_uconst, 0x81, 0x01}), true,
+         DwarfExprEval::Completion::kSync, DwarfStackEntry(double_type, 132.14159),
+         DwarfExprEval::ResultType::kPointer);
 }
 
 TEST_F(DwarfExprEvalTest, Shr) {
   // 8 >> 1 = 4
   DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_shr}, true,
-             DwarfExprEval::Completion::kSync, 4u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit8, DW_OP_lit1, DW_OP_shr");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(4u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit1, DW_OP_shr");
 }
 
 TEST_F(DwarfExprEvalTest, Shra) {
   // -7 (=0b1111...1111001) >> 2 = -2 (=0b1111...1110)
   DoEvalTest({llvm::dwarf::DW_OP_lit7, llvm::dwarf::DW_OP_neg, llvm::dwarf::DW_OP_lit2,
               llvm::dwarf::DW_OP_shra},
-             true, DwarfExprEval::Completion::kSync, static_cast<DwarfExprEval::StackEntry>(-2),
+             true, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-2)),
              DwarfExprEval::ResultType::kPointer, "DW_OP_lit7, DW_OP_neg, DW_OP_lit2, DW_OP_shra");
 }
 
 TEST_F(DwarfExprEvalTest, Shl) {
   // 8 << 1 = 16
   DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_shl}, true,
-             DwarfExprEval::Completion::kSync, 16u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit8, DW_OP_lit1, DW_OP_shl");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(16u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit1, DW_OP_shl");
 }
 
 TEST_F(DwarfExprEvalTest, Xor) {
   // 7 (=0b111) ^ 9 (=0b1001) = 14 (=0b1110)
   DoEvalTest({llvm::dwarf::DW_OP_lit7, llvm::dwarf::DW_OP_lit9, llvm::dwarf::DW_OP_xor}, true,
-             DwarfExprEval::Completion::kSync, 14u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit7, DW_OP_lit9, DW_OP_xor");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(14u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit7, DW_OP_lit9, DW_OP_xor");
 }
 
 TEST_F(DwarfExprEvalTest, Skip) {
@@ -675,23 +771,24 @@
 
   // Skip 0 (execute next instruction which just gives a constant).
   DoEvalTest({llvm::dwarf::DW_OP_skip, 0, 0, llvm::dwarf::DW_OP_lit9}, true,
-             DwarfExprEval::Completion::kSync, 9u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_skip(0), DW_OP_lit9");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(9u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_skip(0), DW_OP_lit9");
 
   // Skip 1 (skip over user-defined instruction which would normally give an error).
   DoEvalTest({llvm::dwarf::DW_OP_skip, 1, 0, llvm::dwarf::DW_OP_hi_user, llvm::dwarf::DW_OP_lit9},
-             true, DwarfExprEval::Completion::kSync, 9u, DwarfExprEval::ResultType::kPointer,
+             true, DwarfExprEval::Completion::kSync, DwarfStackEntry(9u),
+             DwarfExprEval::ResultType::kPointer,
              "DW_OP_skip(1), INVALID_OPCODE(0xff), DW_OP_lit9");
 
   // Skip to the end should just terminate the program. The result when nothing is left on the stack
   // is 0.
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_skip, 1, 0, llvm::dwarf::DW_OP_nop}, true,
-             DwarfExprEval::Completion::kSync, 0, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_skip(1), DW_OP_nop");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_skip(1), DW_OP_nop");
 
   // Skip before the beginning is an error.
-  DoEvalTest({llvm::dwarf::DW_OP_skip, 0, 0xff}, false, DwarfExprEval::Completion::kSync, 0,
-             DwarfExprEval::ResultType::kPointer, "DW_OP_skip(-256)",
+  DoEvalTest({llvm::dwarf::DW_OP_skip, 0, 0xff}, false, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(0), DwarfExprEval::ResultType::kPointer, "DW_OP_skip(-256)",
              "DWARF expression skips out-of-bounds.");
 }
 
@@ -699,92 +796,113 @@
   // 0 @ top of stack means don't take the branch. This jumps out of bounds which should not be
   // taken.
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_bra, 0xff, 0, llvm::dwarf::DW_OP_lit9},
-             true, DwarfExprEval::Completion::kSync, 9u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_bra(255), DW_OP_lit9");
+             true, DwarfExprEval::Completion::kSync, DwarfStackEntry(9u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_bra(255), DW_OP_lit9");
   EXPECT_TRUE(eval().result_is_constant());
 
   // Nonzero means take the branch. This jumps over a user-defined instruction which would give an
   // error if executed.
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_bra, 1, 0, llvm::dwarf::DW_OP_lo_user,
               llvm::dwarf::DW_OP_lit9},
-             true, DwarfExprEval::Completion::kSync, 9u, DwarfExprEval::ResultType::kPointer,
+             true, DwarfExprEval::Completion::kSync, DwarfStackEntry(9u),
+             DwarfExprEval::ResultType::kPointer,
              "DW_OP_lit1, DW_OP_bra(1), DW_OP_GNU_push_tls_address, DW_OP_lit9");
 }
 
 TEST_F(DwarfExprEvalTest, Eq) {
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_eq}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_lit0, DW_OP_eq");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit0, DW_OP_eq");
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_eq}, true,
-             DwarfExprEval::Completion::kSync, 0u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_lit1, DW_OP_eq");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit1, DW_OP_eq");
+
+  // Can't compare different types (this is a signed and a float value).
+  auto uint_type = MakeUint32Type();
+  auto float_type = MakeFloatType();
+  eval().Clear();
+  eval().Push(DwarfStackEntry(uint_type, uint128_t(17u)));
+  eval().Push(DwarfStackEntry(float_type, 17.0f));
+  DoEval(DwarfExpr({llvm::dwarf::DW_OP_eq}), false, DwarfExprEval::Completion::kSync,
+         DwarfStackEntry(0u), DwarfExprEval::ResultType::kPointer);
 }
 
 TEST_F(DwarfExprEvalTest, Ge) {
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_ge}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_lit0, DW_OP_ge");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit0, DW_OP_ge");
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_ge}, true,
-             DwarfExprEval::Completion::kSync, 0u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_lit1, DW_OP_ge");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit1, DW_OP_ge");
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_ge}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit1, DW_OP_lit0, DW_OP_ge");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit0, DW_OP_ge");
+
+  // Can't compare different types (this is an unsigned int and an unsigned char which would be
+  // comparable in C).
+  auto uint_type = MakeUint32Type();
+  auto uchar_type = MakeUnsignedChar8Type();
+  eval().Clear();
+  eval().Push(DwarfStackEntry(uint_type, uint128_t(17u)));
+  eval().Push(DwarfStackEntry(uchar_type, uint128_t(17u)));
+  DoEval(DwarfExpr({llvm::dwarf::DW_OP_ge}), false, DwarfExprEval::Completion::kSync,
+         DwarfStackEntry(0u), DwarfExprEval::ResultType::kPointer);
 }
 
 TEST_F(DwarfExprEvalTest, Gt) {
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_gt}, true,
-             DwarfExprEval::Completion::kSync, 0u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_lit0, DW_OP_gt");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit0, DW_OP_gt");
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_gt}, true,
-             DwarfExprEval::Completion::kSync, 0u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_lit1, DW_OP_gt");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit1, DW_OP_gt");
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_gt}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit1, DW_OP_lit0, DW_OP_gt");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit0, DW_OP_gt");
 }
 
 TEST_F(DwarfExprEvalTest, Le) {
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_le}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_lit0, DW_OP_le");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit0, DW_OP_le");
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_le}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_lit1, DW_OP_le");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit1, DW_OP_le");
   DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_le}, true,
-             DwarfExprEval::Completion::kSync, 0u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit1, DW_OP_lit0, DW_OP_le");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit0, DW_OP_le");
 }
 
 TEST_F(DwarfExprEvalTest, Lt) {
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lt}, true,
-             DwarfExprEval::Completion::kSync, 0u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_lit0, DW_OP_lt");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit0, DW_OP_lt");
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lt}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_lit1, DW_OP_lt");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit1, DW_OP_lt");
 }
 
 TEST_F(DwarfExprEvalTest, Ne) {
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_ne}, true,
-             DwarfExprEval::Completion::kSync, 0u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_lit0, DW_OP_ne");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit0, DW_OP_ne");
   DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_ne}, true,
-             DwarfExprEval::Completion::kSync, 1u, DwarfExprEval::ResultType::kPointer,
-             "DW_OP_lit0, DW_OP_lit1, DW_OP_ne");
+             DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
+             DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit1, DW_OP_ne");
 }
 
 TEST_F(DwarfExprEvalTest, Fbreg) {
   constexpr uint64_t kBase = 0x1000000;
   provider()->set_bp(kBase);
 
-  DoEvalTest({llvm::dwarf::DW_OP_fbreg, 0}, true, DwarfExprEval::Completion::kSync, kBase,
-             DwarfExprEval::ResultType::kPointer, "DW_OP_fbreg(0)");
+  DoEvalTest({llvm::dwarf::DW_OP_fbreg, 0}, true, DwarfExprEval::Completion::kSync,
+             DwarfStackEntry(kBase), DwarfExprEval::ResultType::kPointer, "DW_OP_fbreg(0)");
   EXPECT_FALSE(eval().result_is_constant());
 
   // Note: 129 in SLEB is 0x81, 0x01 (example in DWARF spec).
   DoEvalTest({llvm::dwarf::DW_OP_fbreg, 0x81, 0x01}, true, DwarfExprEval::Completion::kSync,
-             kBase + 129u, DwarfExprEval::ResultType::kPointer, "DW_OP_fbreg(129)");
+             DwarfStackEntry(kBase + 129u), DwarfExprEval::ResultType::kPointer,
+             "DW_OP_fbreg(129)");
   EXPECT_FALSE(eval().result_is_constant());
 }
 
@@ -805,8 +923,8 @@
   memcpy(mem.data(), &kMemoryContents, sizeof(kMemoryContents));
   provider()->AddMemory(kReg6 + kOffsetFromReg6, mem);
 
-  DoEvalTest(program, true, DwarfExprEval::Completion::kAsync, kMemoryContents - 0x30,
-             DwarfExprEval::ResultType::kPointer,
+  DoEvalTest(program, true, DwarfExprEval::Completion::kAsync,
+             DwarfStackEntry(kMemoryContents - 0x30), DwarfExprEval::ResultType::kPointer,
              "DW_OP_breg6(-40), DW_OP_deref, DW_OP_constu(48), DW_OP_minus");
   EXPECT_FALSE(eval().result_is_constant());
 }
@@ -828,8 +946,8 @@
   std::vector<uint8_t> mem{kMemValue, 0xff, 0xff, 0xff, 0xff};
   provider()->AddMemory(kReg1, mem);
 
-  DoEvalTest(program, true, DwarfExprEval::Completion::kAsync, kMemValue + kAddAmount,
-             DwarfExprEval::ResultType::kValue,
+  DoEvalTest(program, true, DwarfExprEval::Completion::kAsync,
+             DwarfStackEntry(kMemValue + kAddAmount), DwarfExprEval::ResultType::kValue,
              "DW_OP_breg1(0), DW_OP_deref_size(1), DW_OP_plus_uconst(2), DW_OP_stack_value");
   EXPECT_FALSE(eval().result_is_constant());
 }
@@ -842,7 +960,7 @@
                                   0x00, 0x50, 0xb8, 0x1e, 0x85, 0xeb, 0x51, 0x98,
                                   0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
   constexpr uint128_t kExpected = (static_cast<uint128_t>(0x4000) << 64) | 0x9851eb851eb85000llu;
-  DoEvalTest(program, true, DwarfExprEval::Completion::kSync, kExpected,
+  DoEvalTest(program, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(kExpected),
              DwarfExprEval::ResultType::kValue, "DW_OP_implicit_value(16, 0x40009851eb851eb85000)");
   EXPECT_TRUE(eval().result_is_constant());
 
@@ -852,7 +970,7 @@
                                       0x00, 0x50, 0xb8, 0x1e, 0x85, 0xeb, 0x51, 0x98,
                                       0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00};
   // clang-format on
-  DoEvalTest(bad_program, false, DwarfExprEval::Completion::kSync, kExpected,
+  DoEvalTest(bad_program, false, DwarfExprEval::Completion::kSync, DwarfStackEntry(kExpected),
              DwarfExprEval::ResultType::kValue,
              "ERROR: \"Not enough data for DWARF implicit value.\"",
              "Not enough data for DWARF implicit value.");
@@ -887,7 +1005,8 @@
   provider()->AddRegisterValue(kDWARFReg3ID, true, 1);   // Original "x" value.
   provider()->AddRegisterValue(kDWARFReg4ID, true, 17);  // "y" value.
 
-  DoEvalTest(program, true, DwarfExprEval::Completion::kSync, 0, DwarfExprEval::ResultType::kData,
+  DoEvalTest(program, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
+             DwarfExprEval::ResultType::kData,
              "DW_OP_breg3(0), DW_OP_lit1, DW_OP_shl, DW_OP_stack_value, DW_OP_piece(4), "
              "DW_OP_reg4, DW_OP_piece(4)");
 
@@ -920,7 +1039,7 @@
     llvm::dwarf::DW_OP_piece, 0x08
   };
   // clang-format on
-  DoEvalTest(mostly_undefined, true, DwarfExprEval::Completion::kSync, 0,
+  DoEvalTest(mostly_undefined, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
              DwarfExprEval::ResultType::kData,
              "DW_OP_piece(16), DW_OP_const1u(32), DW_OP_stack_value, DW_OP_piece(8)");
 
@@ -944,7 +1063,7 @@
   // clang-format on
 
   provider()->AddRegisterValue(kDWARFReg3ID, true, 0x8877665544332211u);  // rbx value.
-  DoEvalTest(partially_defined, true, DwarfExprEval::Completion::kSync, 0,
+  DoEvalTest(partially_defined, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
              DwarfExprEval::ResultType::kData,
              "DW_OP_implicit_value(4, 0x429c0000), DW_OP_piece(4), DW_OP_reg3, DW_OP_piece(1), "
              "DW_OP_piece(11), DW_OP_const1u(32), DW_OP_stack_value, DW_OP_piece(8)");
@@ -979,7 +1098,7 @@
   provider()->set_entry_provider(entry_provider);
   entry_provider->AddRegisterValue(kDWARFReg5ID, true, 0x8877665544332211);
 
-  DoEvalTest(entry_value, true, DwarfExprEval::Completion::kSync, 0,
+  DoEvalTest(entry_value, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
              DwarfExprEval::ResultType::kData,
              "DW_OP_implicit_value(4, 0x429c0000), DW_OP_piece(4), "
              "DW_OP_GNU_entry_value(DW_OP_reg5), DW_OP_stack_value, DW_OP_piece(1), "
@@ -1018,7 +1137,8 @@
   provider()->AddRegisterValue(kDWARFReg3ID, true, 1);             // Original "x" value.
   provider()->AddRegisterValue(kDWARFReg4ID, true, kReg4Address);  // Points to the "y" value.
 
-  DoEvalTest(program, true, DwarfExprEval::Completion::kAsync, 0, DwarfExprEval::ResultType::kData,
+  DoEvalTest(program, true, DwarfExprEval::Completion::kAsync, DwarfStackEntry(0),
+             DwarfExprEval::ResultType::kData,
              "DW_OP_breg3(0), DW_OP_lit1, DW_OP_shl, DW_OP_stack_value, DW_OP_piece(4), "
              "DW_OP_breg4(0), DW_OP_piece(4)");
 
@@ -1033,7 +1153,7 @@
 
   provider()->set_tls_segment(0xdeadbeef);
 
-  DoEvalTest(program, true, DwarfExprEval::Completion::kAsync, 0x7060504e1afbfef,
+  DoEvalTest(program, true, DwarfExprEval::Completion::kAsync, DwarfStackEntry(0x7060504e1afbfef),
              DwarfExprEval::ResultType::kPointer,
              "DW_OP_const8u(0x706050403020100), DW_OP_form_tls_address");
 }
@@ -1067,13 +1187,13 @@
       llvm::dwarf::DW_OP_reg0,
       llvm::dwarf::DW_OP_stack_value,
   };
-  DoEvalTest(simple_program, true, DwarfExprEval::Completion::kSync, kEntryX0,
+  DoEvalTest(simple_program, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(kEntryX0),
              DwarfExprEval::ResultType::kValue, "DW_OP_entry_value(DW_OP_reg0), DW_OP_stack_value");
   eval().Clear();
 
   // An entry value expression with a bad length.
   std::vector<uint8_t> bad_length{llvm::dwarf::DW_OP_entry_value, 23, llvm::dwarf::DW_OP_reg0};
-  DoEvalTest(bad_length, false, DwarfExprEval::Completion::kSync, 0,
+  DoEvalTest(bad_length, false, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
              DwarfExprEval::ResultType::kValue,
              "ERROR: \"DW_OP_entry_value sub expression is a bad length.\"",
              "DW_OP_entry_value sub expression is a bad length.");
@@ -1107,7 +1227,7 @@
   // The outer expression computs (X6 + offset) and then subtracts that from the entry value.
   constexpr uint64_t kExpected = kEntryValue - (kTopX6 + kTopOffset);
 
-  DoEvalTest(complex_program, true, DwarfExprEval::Completion::kAsync, kExpected,
+  DoEvalTest(complex_program, true, DwarfExprEval::Completion::kAsync, DwarfStackEntry(kExpected),
              DwarfExprEval::ResultType::kPointer,
              "DW_OP_entry_value(DW_OP_breg6(49), DW_OP_deref), DW_OP_breg6(1), DW_OP_minus");
 
diff --git a/src/developer/debug/zxdb/symbols/dwarf_stack_entry.cc b/src/developer/debug/zxdb/symbols/dwarf_stack_entry.cc
new file mode 100644
index 0000000..41e981f
--- /dev/null
+++ b/src/developer/debug/zxdb/symbols/dwarf_stack_entry.cc
@@ -0,0 +1,163 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/developer/debug/zxdb/symbols/dwarf_stack_entry.h"
+
+#include <float.h>
+#include <lib/syslog/cpp/macros.h>
+#include <math.h>
+
+#include <algorithm>
+#include <iostream>
+
+namespace zxdb {
+
+DwarfStackEntry::DwarfStackEntry(uint128_t generic_value) : unsigned_value_(generic_value) {}
+
+DwarfStackEntry::DwarfStackEntry(fxl::RefPtr<BaseType> type, int128_t value)
+    : type_(std::move(type)), signed_value_(value) {
+  FX_DCHECK(TreatAsSigned());
+}
+
+DwarfStackEntry::DwarfStackEntry(fxl::RefPtr<BaseType> type, uint128_t value)
+    : type_(std::move(type)), unsigned_value_(value) {
+  FX_DCHECK(TreatAsUnsigned());
+}
+
+DwarfStackEntry::DwarfStackEntry(fxl::RefPtr<BaseType> type, float value)
+    : type_(std::move(type)), float_value_(value) {
+  FX_DCHECK(TreatAsFloat());
+}
+
+DwarfStackEntry::DwarfStackEntry(fxl::RefPtr<BaseType> type, double value)
+    : type_(std::move(type)), double_value_(value) {
+  FX_DCHECK(TreatAsDouble());
+}
+
+bool DwarfStackEntry::operator==(const DwarfStackEntry& other) const {
+  if (is_generic() != other.is_generic())
+    return false;
+
+  if (!is_generic()) {
+    // Validate base type and byte size.
+    if (type_->base_type() != other.type_->base_type() ||
+        type_->byte_size() != other.type_->byte_size())
+      return false;
+  }
+
+  if (TreatAsUnsigned())
+    return unsigned_value_ == other.unsigned_value_;
+  if (TreatAsSigned())
+    return signed_value_ == other.signed_value_;
+
+  // This is used for tests that compare the results of expressions. The floating-point error can
+  // accumulate much larger than DBL_EPSILON so we have our own more permissive value. If necessary,
+  // this can get much fancier, gtest does some more rigorous comparisons in its ASSERT_DOUBLE_EQ.
+  constexpr double kEpsilon = 0.000000001;
+
+  if (TreatAsFloat()) {
+    if (isnan(float_value_) || isnan(other.float_value_))
+      return false;
+    return fabsf(float_value_ - other.float_value_) < kEpsilon;
+  }
+  if (TreatAsDouble()) {
+    if (isnan(double_value_) || isnan(other.double_value_))
+      return false;
+    return fabs(double_value_ - other.double_value_) < kEpsilon;
+  }
+
+  FX_NOTREACHED();
+  return false;
+}
+
+size_t DwarfStackEntry::GetByteSize() const {
+  if (type_) {
+    // In case the type info specifies something like a 256 bit integer, clamp the size to the
+    // maximum size of our data.
+    return std::min<size_t>(sizeof(UnsignedType), type_->byte_size());
+  }
+  return sizeof(UnsignedType);
+}
+
+// static
+bool DwarfStackEntry::TreatAsSigned(const BaseType* type) {
+  if (!type)
+    return false;  // Generic types are unsigned.
+  return type->base_type() == BaseType::kBaseTypeSigned ||
+         type->base_type() == BaseType::kBaseTypeSignedChar;
+}
+
+// static
+bool DwarfStackEntry::TreatAsUnsigned(const BaseType* type) {
+  if (!type)
+    return true;  // Generic types are unsigned.
+  return type->base_type() == BaseType::kBaseTypeAddress ||
+         type->base_type() == BaseType::kBaseTypeBoolean ||
+         type->base_type() == BaseType::kBaseTypeUnsigned ||
+         type->base_type() == BaseType::kBaseTypeUnsignedChar ||
+         type->base_type() == BaseType::kBaseTypeUTF;
+}
+
+// static
+bool DwarfStackEntry::TreatAsFloat(const BaseType* type) {
+  if (!type)
+    return false;  // Generic types are unsigned.
+  return type->base_type() == BaseType::kBaseTypeFloat && type->byte_size() == 4;
+}
+
+// static
+bool DwarfStackEntry::TreatAsDouble(const BaseType* type) {
+  if (!type)
+    return false;  // Generic types are unsigned.
+  return type->base_type() == BaseType::kBaseTypeFloat && type->byte_size() == 8;
+}
+
+bool DwarfStackEntry::IsZero() const {
+  if (TreatAsSigned())
+    return signed_value_ == 0;
+  if (TreatAsUnsigned())
+    return unsigned_value_ == 0;
+  if (TreatAsFloat())
+    return !isnan(float_value_) && float_value_ > -FLT_EPSILON && float_value_ < FLT_EPSILON;
+  if (TreatAsDouble())
+    return !isnan(double_value_) && double_value_ > -DBL_EPSILON && double_value_ < DBL_EPSILON;
+
+  FX_NOTREACHED();
+  return false;
+}
+
+bool DwarfStackEntry::SameTypeAs(const DwarfStackEntry& other) const {
+  if (is_generic() && other.is_generic())
+    return true;
+
+  if (is_generic() || other.is_generic())
+    return false;  // One is generic and the other isn't they can't match.
+
+  // Both are declared types, the types and sizes must match.
+  return (type_->base_type() == other.type_->base_type()) &&
+         (type_->byte_size() == other.type_->byte_size());
+}
+
+std::string DwarfStackEntry::GetTypeDescription() const {
+  if (is_generic())
+    return "generic";
+  return BaseType::BaseTypeToString(type_->base_type()) +
+         "(size=" + std::to_string(type_->byte_size()) + ")";
+}
+
+std::ostream& operator<<(std::ostream& out, const DwarfStackEntry& entry) {
+  out << "DwarfStackEntry(type=" << entry.GetTypeDescription() << ", value=";
+  if (entry.TreatAsUnsigned()) {
+    out << to_string(entry.unsigned_value());
+  } else if (entry.TreatAsSigned()) {
+    out << to_string(entry.signed_value());
+  } else if (entry.TreatAsFloat()) {
+    out << std::to_string(entry.float_value());
+  } else if (entry.TreatAsDouble()) {
+    out << std::to_string(entry.double_value());
+  }
+  return out << ")";
+}
+
+}  // namespace zxdb
diff --git a/src/developer/debug/zxdb/symbols/dwarf_stack_entry.h b/src/developer/debug/zxdb/symbols/dwarf_stack_entry.h
new file mode 100644
index 0000000..c4a283b
--- /dev/null
+++ b/src/developer/debug/zxdb/symbols/dwarf_stack_entry.h
@@ -0,0 +1,115 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_DEVELOPER_DEBUG_ZXDB_SYMBOLS_DWARF_STACK_ENTRY_H_
+#define SRC_DEVELOPER_DEBUG_ZXDB_SYMBOLS_DWARF_STACK_ENTRY_H_
+
+#include <iosfwd>
+
+#include "src/developer/debug/zxdb/common/int128_t.h"
+#include "src/developer/debug/zxdb/symbols/base_type.h"
+
+namespace zxdb {
+
+// Represents an entry in the stack for evaluating DWARF 5 expressions.
+//
+// DWARF 5 introduced "typed" stack entries. Previously, all values were of a generic type. This
+// means that every entry has a value plus an optional type which is a reference to a "base" type
+//
+//   "Each element of the stack has a type and a value, and can represent a value of any supported
+//   base type of the target machine. Instead of a base type, elements can have a generic type,
+//   which is an integral type that has the size of an address on the target machine and unspecified
+//   signedness."
+//
+// We treat these different values as either signed, unsigned, float, or double. The generic type
+// and bools are stored as unsigned.
+class DwarfStackEntry {
+ public:
+  // The DWARF spec says the stack entry "can represent a value of any supported base type of the
+  // target machine". We need to support x87 long doubles (80 bits) and XMM registers (128 bits).
+  // Generally the XMM registers used for floating point use only the low 64 bits and long doubles
+  // are very uncommon, but using 128 bits here covers the edge cases better. The ARM "v" registers
+  // (128 bits) are similar.
+  //
+  // The YMM (256 bit) and ZMM (512 bit) x64 reigisters aren't currently representable in DWARF
+  // expressions so larger numbers are unnecessary.
+  using SignedType = int128_t;
+  using UnsignedType = uint128_t;
+
+  explicit DwarfStackEntry(UnsignedType generic_value);
+
+  // The sign of the BaseType in the first argument must match the sign of the second argument.
+  DwarfStackEntry(fxl::RefPtr<BaseType> type, SignedType value);
+  DwarfStackEntry(fxl::RefPtr<BaseType> type, UnsignedType value);  // type can be null for generic.
+  DwarfStackEntry(fxl::RefPtr<BaseType> type, float value);
+  DwarfStackEntry(fxl::RefPtr<BaseType> type, double value);
+
+  // Comparison for unit testing. If types are present, the base type enum and byte size are
+  // compared, but not the name nor the identity of the type record.
+  bool operator==(const DwarfStackEntry& other) const;
+  bool operator!=(const DwarfStackEntry& other) const { return !operator==(other); }
+
+  bool is_generic() const { return !type_; }
+  const BaseType* type() const { return type_.get(); }      // Possibly null.
+  fxl::RefPtr<BaseType> type_ref() const { return type_; }  // Possibly null.
+
+  // Returns the size in bytes of this value.
+  size_t GetByteSize() const;
+
+  UnsignedType unsigned_value() const { return unsigned_value_; }
+  SignedType signed_value() const { return signed_value_; }
+  float float_value() const { return float_value_; }
+  double double_value() const { return double_value_; }
+
+  // These functions also accept null BaseType pointers which are counted as generic.
+  static bool TreatAsSigned(const BaseType* type);
+  static bool TreatAsUnsigned(const BaseType* type);
+  static bool TreatAsFloat(const BaseType* type);
+  static bool TreatAsDouble(const BaseType* type);
+
+  bool TreatAsSigned() const { return TreatAsSigned(type_.get()); }
+  bool TreatAsUnsigned() const { return TreatAsUnsigned(type_.get()); }
+  bool TreatAsFloat() const { return TreatAsFloat(type_.get()); }
+  bool TreatAsDouble() const { return TreatAsDouble(type_.get()); }
+
+  bool is_integral() const { return TreatAsSigned() || TreatAsUnsigned(); }
+
+  // Returns true if the value is within the machine epsilon of 0 for the current type.
+  bool IsZero() const;
+
+  // Returns true if the two stack entries have the same type, either the same base type, or they
+  // are both generic. Most arithmetic operations require them to be the same.
+  bool SameTypeAs(const DwarfStackEntry& other) const;
+
+  std::string GetTypeDescription() const;
+
+ private:
+  // Null indicates "generic".
+  fxl::RefPtr<BaseType> type_;
+
+  // When a type is given, only the low X bytes are relevant (where X is the byte size of the given
+  // type). However, the value should be a valid integer (the unused bits will be 0 in the unsigned
+  // case, and sign-extended in the signed case).
+  //
+  // Generic values are treated as unsigned.
+  //
+  // We do not currently support non-integral stack entries. These are not currently generated by
+  // the compiler.
+  //
+  // NOTE: Some users expect this to be a union! If you know the byte size of the result, some
+  // users extract the output as an unsigned and memcpy it to the result to avoid type-checking.
+  union {
+    UnsignedType unsigned_value_;  // Address, boolean, unsigned, unsigned char, UTF.
+    SignedType signed_value_;      // Signed, signed char.
+    float float_value_;            // Float, 32-bit.
+    double double_value_;          // Float, 64-bit.
+  };
+};
+
+// For test output.
+std::ostream& operator<<(std::ostream& out, const DwarfStackEntry& entry);
+
+}  // namespace zxdb
+
+#endif  // SRC_DEVELOPER_DEBUG_ZXDB_SYMBOLS_DWARF_STACK_ENTRY_H_
diff --git a/src/developer/debug/zxdb/symbols/module_symbols_impl.cc b/src/developer/debug/zxdb/symbols/module_symbols_impl.cc
index 9ca97d7..30dc357 100644
--- a/src/developer/debug/zxdb/symbols/module_symbols_impl.cc
+++ b/src/developer/debug/zxdb/symbols/module_symbols_impl.cc
@@ -613,10 +613,14 @@
       eval.GetResultType() != DwarfExprEval::ResultType::kPointer)
     return Location(symbol_context, std::move(variable));
 
+  DwarfStackEntry result = eval.GetResult();
+  if (!result.TreatAsUnsigned())
+    return Location(symbol_context, std::move(variable));
+
   // TODO(brettw) in all of the return cases we could in the future fill in the file/line of the
   // definition of the variable. Currently Variables don't provide that (even though it's usually in
   // the DWARF symbols).
-  return Location(eval.GetResult(), FileLine(), 0, symbol_context, std::move(variable));
+  return Location(result.unsigned_value(), FileLine(), 0, symbol_context, std::move(variable));
 }
 
 std::vector<Location> ModuleSymbolsImpl::ResolvePltName(const SymbolContext& symbol_context,
diff --git a/src/developer/debug/zxdb/symbols/type_test_support.cc b/src/developer/debug/zxdb/symbols/type_test_support.cc
index 08b2fce..6e245d2 100644
--- a/src/developer/debug/zxdb/symbols/type_test_support.cc
+++ b/src/developer/debug/zxdb/symbols/type_test_support.cc
@@ -48,6 +48,10 @@
   return fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeSignedChar, 1, "char");
 }
 
+fxl::RefPtr<BaseType> MakeUnsignedChar8Type() {
+  return fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeUnsignedChar, 1, "unsigned char");
+}
+
 fxl::RefPtr<BaseType> MakeRustCharType() {
   auto type = fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeUnsignedChar, 4, "char");
   type->set_parent(UncachedLazySymbol::MakeUnsafe(MakeRustUnit()));
diff --git a/src/developer/debug/zxdb/symbols/type_test_support.h b/src/developer/debug/zxdb/symbols/type_test_support.h
index 9988fb4..c0a713d 100644
--- a/src/developer/debug/zxdb/symbols/type_test_support.h
+++ b/src/developer/debug/zxdb/symbols/type_test_support.h
@@ -36,6 +36,7 @@
 fxl::RefPtr<BaseType> MakeDoubleType();
 
 fxl::RefPtr<BaseType> MakeSignedChar8Type();
+fxl::RefPtr<BaseType> MakeUnsignedChar8Type();
 
 fxl::RefPtr<BaseType> MakeRustCharType();
 fxl::RefPtr<ModifiedType> MakeRustCharPointerType();