是否可以在SMLNJ中检查指针是否相等(用于调试)?

时间:2020-05-14 11:39:53

标签: reference sml memoization smlnj

假设我们具有以下玩具二叉树结构:

datatype Tree = Leaf | Branch of Tree * Tree
fun left(Branch(l,r))= l
fun right(Branch(l,r))= r

并假设我们有一些大而昂贵的计算树

val c: Tree= …
val d: Tree= Branch(c,c)

我们可以在SML / NJ解释器中验证left(d)right(d)确实指向内存中的同一位置吗?

(此问题源于处理可能包含周期的惰性流,并尝试调试备忘录是否正常工作。)

1 个答案:

答案 0 :(得分:2)

我认为我们可以通过使用 Unsafe.cast 将两个值都转换为 word 来实现,这将指针重新解释为可以与 = 进行比较的数字。这是实现此想法的 is 函数:

infix 4 is (* = > < >= ... *)
fun op is(a: 'a, b: 'a) = (Unsafe.cast a: word) = Unsafe.cast b

注意:

  • 我需要注释 (a, b) 以确保类型检查器将参数限制为相同类型,否则 is 的 typeig 将是 'a * 'b -> bool
  • 我需要注释第一个 Unsafe.cast 应用程序,以防止 SML/NJ 不得不使用 polyEqual 从而避免发出 Warning: calling polyEqual
  • 类型推断可以很好地解决其余问题

以下示例说明了向量中的结构共享:

local
  fun const a _ = a
  val v = Vector.tabulate(1000, const #[1,2,3])
  val v = Vector.update(v, 230, #[1,2,3]) (* same value, but new allocation *)
in
  val test1 = Vector.sub(v, 0) is Vector.sub(v, 999)
  val test2 = Vector.sub(v, 0) is Vector.sub(v, 230)
end

它确实按预期工作。回复回复

(* val test1 = true : bool
   val test2 = false : bool *)

现在这是您问题中的树示例:

local
  datatype Tree
    = Leaf
    | Branch of Tree * Tree

  fun left (Branch(l,r)) = l
  fun right (Branch(l,r)) = r

  val c = Branch(Leaf,Leaf) (* imagine it being something complex *)
  val d = Branch(c, c)
in
  val test3 = left d is right d
end

当我们尝试时,它会正确回答:

(* val test3 = true : bool *)

我认为这回答了您的问题。下面我将讨论 word 的选择以及转换到它时可能在内部发生的事情


据我所知,SML/NJ 像许多 lisps、v8、OCaml 等一样进行指针标记。能够读取指针值,而不是误解堆对象。

我认为 word 可以很好地用于此目的;它像 int 一样立即,而 unsigned 不像它......所以它应该对应于内存地址(不要阻止我)。

似乎有一个错误*阻止您直接在 repl 中检查单词值,可能是指针标记在起作用。
* 至少编译器将其报告为那样?从 v110.99 开始

一种解决方法是立即将值转换为不同的表示形式(也许需要装箱?),例如字符串或 Word64.word

fun addrOf x = Word.toString (Unsafe.cast x)

确实,当我们尝试使用新定义的 addrOf 函数来比较地址与其字符串化值时,我们可以观察到指针标记的效果

(* We'll need these definitions onwards, might as well have them here: *)

infix 5 >> <<
val op >> = Word.>>
val op << = Word.<<
val unsafeWord = Option.valOf o Word.fromString
local
  val x = SOME 31 (* dummy boxed value *)
  val addr = unsafeWord (addrOf x)
in
  val test4 = Unsafe.cast x = addr
  val test5 = Unsafe.cast x >> 0w1 = addr >> 0w1 (* get rid of lowest bit *)
end

(* val test4 = false : bool
   val test5 = true : bool *)

那么,如果标签只是 SML/NJ 中机器字的最低位,就像在许多标签指针实现中一样,那么指针应该准确地是转换值右移一次,然后再次离开。

fun addrOf x = Unsafe.cast x >> 0w1 << 0w1

我们之所以进行这种看似 nop 的转换(请记住,所有指针都是偶数)是因为它在过程中正确标记了转换字值。
如果我们先左移然后右移,标签本身会通过第一个操作找到它的值,因为强制指针变成了正确的词。这就是为什么我们先右移。位,因此不会丢失有关地址的信息,但内部正确存在立即值标记。

local
  fun strAddrOf x = Word.toString (Unsafe.cast x)
  fun isEven x = Word.andb (x, 0w1) = 0w0

  val x = SOME 42
  val ogAddr = unsafeWord (strAddrOf x) (* a known-correct conversion: no shifting takes place *)
  val badAddr = Unsafe.cast x << 0w1 >> 0w1
  val goodAddr = Unsafe.cast x >> 0w1 << 0w1
in
  val test6 = ogAddr = badAddr
  val test7 = ogAddr = goodAddr
  val test8 = isEven ogAddr
end

(* val test6 = false : bool
   val test7 = true : bool
   val test8 = true : bool *)

addrOf 中的这种移位允许您直接获取指针值,而无需中间转换(和装箱)到 stringword64。当然,此解决方案会分解为实际未装箱的值,因此最好在您的 Unsafe.boxed 定义中测试对象是否以 (addrOf) 开头,并返回 0wx0 如果您正在使用立即数。

希望这对您有用。到目前为止,它确实对我有用!

相关问题