From 214cdd0deeab903df5b8b1b4b853a6f6f5e6cb7d Mon Sep 17 00:00:00 2001
From: Brian Anderson <banderson@mozilla.com>
Date: Mon, 13 Feb 2012 16:06:56 -0800
Subject: [PATCH] rustc: Translate crust functions

---
 src/comp/back/upcall.rs              |   4 +
 src/comp/middle/trans/base.rs        |  32 ++-
 src/comp/middle/trans/native.rs      | 315 ++++++++++++++++++++-------
 src/rt/rust_builtin.cpp              |   4 +-
 src/test/run-pass/crust-call-deep.rs |  23 ++
 src/test/run-pass/crust-call.rs      |  23 ++
 6 files changed, 315 insertions(+), 86 deletions(-)
 create mode 100644 src/test/run-pass/crust-call-deep.rs
 create mode 100644 src/test/run-pass/crust-call.rs

diff --git a/src/comp/back/upcall.rs b/src/comp/back/upcall.rs
index e3a124640e4..7e86191caf6 100644
--- a/src/comp/back/upcall.rs
+++ b/src/comp/back/upcall.rs
@@ -28,6 +28,7 @@ type upcalls =
      dynastack_free: ValueRef,
      alloc_c_stack: ValueRef,
      call_shim_on_c_stack: ValueRef,
+     call_shim_on_rust_stack: ValueRef,
      rust_personality: ValueRef,
      reset_stack_limit: ValueRef};
 
@@ -106,6 +107,9 @@ fn declare_upcalls(targ_cfg: @session::config,
                 // arguments: void *args, void *fn_ptr
                 [T_ptr(T_i8()), T_ptr(T_i8())],
                 int_t),
+          call_shim_on_rust_stack:
+              d("call_shim_on_rust_stack",
+                [T_ptr(T_i8()), T_ptr(T_i8())], int_t),
           rust_personality:
               d("rust_personality", [], T_i32()),
           reset_stack_limit:
diff --git a/src/comp/middle/trans/base.rs b/src/comp/middle/trans/base.rs
index a1656d50e8e..f5f924b5691 100644
--- a/src/comp/middle/trans/base.rs
+++ b/src/comp/middle/trans/base.rs
@@ -4193,14 +4193,17 @@ fn copy_args_to_allocas(fcx: @fn_ctxt, bcx: @block_ctxt, args: [ast::arg],
 // Ties up the llstaticallocas -> llloadenv -> llderivedtydescs ->
 // lldynamicallocas -> lltop edges, and builds the return block.
 fn finish_fn(fcx: @fn_ctxt, lltop: BasicBlockRef) {
+    tie_up_header_blocks(fcx, lltop);
+    let ret_cx = new_raw_block_ctxt(fcx, fcx.llreturn);
+    trans_fn_cleanups(fcx, ret_cx);
+    RetVoid(ret_cx);
+}
+
+fn tie_up_header_blocks(fcx: @fn_ctxt, lltop: BasicBlockRef) {
     Br(new_raw_block_ctxt(fcx, fcx.llstaticallocas), fcx.llloadenv);
     Br(new_raw_block_ctxt(fcx, fcx.llloadenv), fcx.llderivedtydescs_first);
     Br(new_raw_block_ctxt(fcx, fcx.llderivedtydescs), fcx.lldynamicallocas);
     Br(new_raw_block_ctxt(fcx, fcx.lldynamicallocas), lltop);
-
-    let ret_cx = new_raw_block_ctxt(fcx, fcx.llreturn);
-    trans_fn_cleanups(fcx, ret_cx);
-    RetVoid(ret_cx);
 }
 
 enum self_arg { impl_self(ty::t), no_self, }
@@ -4552,13 +4555,20 @@ fn param_bounds(ccx: @crate_ctxt, tp: ast::ty_param) -> ty::param_bounds {
     ccx.tcx.ty_param_bounds.get(tp.id)
 }
 
-fn register_fn_full(ccx: @crate_ctxt, sp: span, path: path, _flav: str,
+fn register_fn_full(ccx: @crate_ctxt, sp: span, path: path, flav: str,
                     tps: [ast::ty_param], node_id: ast::node_id,
                     node_type: ty::t) {
     let llfty = type_of_fn_from_ty(ccx, node_type,
                                    vec::map(tps, {|p| param_bounds(ccx, p)}));
+    register_fn_fuller(ccx, sp, path, flav, node_id, node_type,
+                       lib::llvm::CCallConv, llfty);
+}
+
+fn register_fn_fuller(ccx: @crate_ctxt, sp: span, path: path, _flav: str,
+                      node_id: ast::node_id, node_type: ty::t,
+                      cc: lib::llvm::CallConv, llfty: TypeRef) {
     let ps: str = mangle_exported_name(ccx, path, node_type);
-    let llfn: ValueRef = decl_cdecl_fn(ccx.llmod, ps, llfty);
+    let llfn: ValueRef = decl_fn(ccx.llmod, ps, cc, llfty);
     ccx.item_ids.insert(node_id, llfn);
     ccx.item_symbols.insert(node_id, ps);
 
@@ -4747,9 +4757,13 @@ fn collect_item(ccx: @crate_ctxt, abi: @mutable option<ast::native_abi>,
           either::right(abi_) { *abi = option::some(abi_); }
         }
       }
-      ast::item_fn(_, tps, _) {
-        register_fn(ccx, i.span, my_path, "fn", tps,
-                    i.id);
+      ast::item_fn(decl, tps, _) {
+        if decl.purity != ast::crust_fn {
+            register_fn(ccx, i.span, my_path, "fn", tps,
+                        i.id);
+        } else {
+            native::register_crust_fn(ccx, i.span, my_path, i.id);
+        }
       }
       ast::item_impl(tps, _, _, methods) {
         let path = my_path + [path_name(int::str(i.id))];
diff --git a/src/comp/middle/trans/native.rs b/src/comp/middle/trans/native.rs
index 93f753455bf..3830c363879 100644
--- a/src/comp/middle/trans/native.rs
+++ b/src/comp/middle/trans/native.rs
@@ -1,13 +1,15 @@
 import driver::session::session;
+import syntax::codemap::span;
 import ctypes::c_uint;
 import front::attr;
 import lib::llvm::{ llvm, TypeRef, ValueRef };
 import syntax::ast;
+import back::link;
 import common::*;
 import build::*;
 import base::*;
 
-export link_name, trans_native_mod, trans_crust_fn;
+export link_name, trans_native_mod, register_crust_fn, trans_crust_fn;
 
 fn link_name(i: @ast::native_item) -> str {
     alt attr::get_meta_item_value_str_by_name(i.attrs, "link_name") {
@@ -20,34 +22,110 @@ type c_stack_tys = {
     arg_tys: [TypeRef],
     ret_ty: TypeRef,
     ret_def: bool,
-    base_fn_ty: TypeRef,
     bundle_ty: TypeRef,
     shim_fn_ty: TypeRef
 };
 
-fn c_stack_tys(ccx: @crate_ctxt,
-               id: ast::node_id) -> @c_stack_tys {
+fn c_arg_and_ret_lltys(ccx: @crate_ctxt,
+                       id: ast::node_id) -> ([TypeRef], TypeRef, ty::t) {
     alt ty::get(ty::node_id_to_type(ccx.tcx, id)).struct {
       ty::ty_fn({inputs: arg_tys, output: ret_ty, _}) {
         let llargtys = type_of_explicit_args(ccx, arg_tys);
         let llretty = type_of(ccx, ret_ty);
-        let bundle_ty = T_struct(llargtys + [T_ptr(llretty)]);
-        ret @{
-            arg_tys: llargtys,
-            ret_ty: llretty,
-            ret_def: !ty::type_is_bot(ret_ty) && !ty::type_is_nil(ret_ty),
-            base_fn_ty: T_fn(llargtys, llretty),
-            bundle_ty: bundle_ty,
-            shim_fn_ty: T_fn([T_ptr(bundle_ty)], T_void())
-        };
+        (llargtys, llretty, ret_ty)
       }
       _ {
           // Precondition?
-          ccx.tcx.sess.bug("c_stack_tys called on non-function type");
+          ccx.tcx.sess.bug("c_arg_and_ret_lltys called on non-function type");
       }
     }
 }
 
+fn c_stack_tys(ccx: @crate_ctxt,
+               id: ast::node_id) -> @c_stack_tys {
+    let (llargtys, llretty, ret_ty) = c_arg_and_ret_lltys(ccx, id);
+    let bundle_ty = T_struct(llargtys + [T_ptr(llretty)]);
+    ret @{
+        arg_tys: llargtys,
+        ret_ty: llretty,
+        ret_def: !ty::type_is_bot(ret_ty) && !ty::type_is_nil(ret_ty),
+        bundle_ty: bundle_ty,
+        shim_fn_ty: T_fn([T_ptr(bundle_ty)], T_void())
+    };
+}
+
+type shim_arg_builder = fn(bcx: @block_ctxt, tys: @c_stack_tys,
+                           llargbundle: ValueRef) -> [ValueRef];
+
+type shim_ret_builder = fn(bcx: @block_ctxt, tys: @c_stack_tys,
+                           llargbundle: ValueRef, llretval: ValueRef);
+
+fn build_shim_fn_(ccx: @crate_ctxt,
+                  shim_name: str,
+                  llbasefn: ValueRef,
+                  tys: @c_stack_tys,
+                  cc: lib::llvm::CallConv,
+                  arg_builder: shim_arg_builder,
+                  ret_builder: shim_ret_builder) -> ValueRef {
+
+    let llshimfn = decl_internal_cdecl_fn(
+        ccx.llmod, shim_name, tys.shim_fn_ty);
+
+    // Declare the body of the shim function:
+    let fcx = new_fn_ctxt(ccx, [], llshimfn, none);
+    let bcx = new_top_block_ctxt(fcx, none);
+    let lltop = bcx.llbb;
+    let llargbundle = llvm::LLVMGetParam(llshimfn, 0 as c_uint);
+    let llargvals = arg_builder(bcx, tys, llargbundle);
+
+    // Create the call itself and store the return value:
+    let llretval = CallWithConv(bcx, llbasefn,
+                                llargvals, cc); // r
+
+    ret_builder(bcx, tys, llargbundle, llretval);
+
+    build_return(bcx);
+    finish_fn(fcx, lltop);
+
+    ret llshimfn;
+}
+
+type wrap_arg_builder = fn(bcx: @block_ctxt, tys: @c_stack_tys,
+                           llwrapfn: ValueRef,
+                           llargbundle: ValueRef);
+
+type wrap_ret_builder = fn(bcx: @block_ctxt, tys: @c_stack_tys,
+                           llargbundle: ValueRef);
+
+fn build_wrap_fn_(ccx: @crate_ctxt,
+                  tys: @c_stack_tys,
+                  llshimfn: ValueRef,
+                  llwrapfn: ValueRef,
+                  shim_upcall: ValueRef,
+                  arg_builder: wrap_arg_builder,
+                  ret_builder: wrap_ret_builder) {
+
+    let fcx = new_fn_ctxt(ccx, [], llwrapfn, none);
+    let bcx = new_top_block_ctxt(fcx, none);
+    let lltop = bcx.llbb;
+
+    // Allocate the struct and write the arguments into it.
+    let llargbundle = alloca(bcx, tys.bundle_ty);
+    arg_builder(bcx, tys, llwrapfn, llargbundle);
+
+    // Create call itself.
+    let llshimfnptr = PointerCast(bcx, llshimfn, T_ptr(T_i8()));
+    let llrawargbundle = PointerCast(bcx, llargbundle, T_ptr(T_i8()));
+    Call(bcx, shim_upcall, [llrawargbundle, llshimfnptr]);
+    ret_builder(bcx, tys, llargbundle);
+
+    tie_up_header_blocks(fcx, lltop);
+
+    // Make sure our standard return block (that we didn't use) is terminated
+    let ret_cx = new_raw_block_ctxt(fcx, fcx.llreturn);
+    Unreachable(ret_cx);
+}
+
 // For each native function F, we generate a wrapper function W and a shim
 // function S that all work together.  The wrapper function W is the function
 // that other rust code actually invokes.  Its job is to marshall the
@@ -89,46 +167,41 @@ fn trans_native_mod(ccx: @crate_ctxt,
                      native_item: @ast::native_item,
                      tys: @c_stack_tys,
                      cc: lib::llvm::CallConv) -> ValueRef {
+
+        fn build_args(bcx: @block_ctxt, tys: @c_stack_tys,
+                      llargbundle: ValueRef) -> [ValueRef] {
+            let llargvals = [];
+            let i = 0u;
+            let n = vec::len(tys.arg_tys);
+            while i < n {
+                let llargval = load_inbounds(bcx, llargbundle, [0, i as int]);
+                llargvals += [llargval];
+                i += 1u;
+            }
+            ret llargvals;
+        }
+
+        fn build_ret(bcx: @block_ctxt, tys: @c_stack_tys,
+                     llargbundle: ValueRef, llretval: ValueRef)  {
+            if tys.ret_def {
+                let n = vec::len(tys.arg_tys);
+                // R** llretptr = &args->r;
+                let llretptr = GEPi(bcx, llargbundle, [0, n as int]);
+                // R* llretloc = *llretptr; /* (args->r) */
+                let llretloc = Load(bcx, llretptr);
+                // *args->r = r;
+                Store(bcx, llretval, llretloc);
+            }
+        }
+
         let lname = link_name(native_item);
-
         // Declare the "prototype" for the base function F:
-        let llbasefn = decl_fn(ccx.llmod, lname, cc, tys.base_fn_ty);
-
-        // Create the shim function:
+        let llbasefnty = T_fn(tys.arg_tys, tys.ret_ty);
+        let llbasefn = decl_fn(ccx.llmod, lname, cc, llbasefnty);
+        // Name the shim function
         let shim_name = lname + "__c_stack_shim";
-        let llshimfn = decl_internal_cdecl_fn(
-            ccx.llmod, shim_name, tys.shim_fn_ty);
-
-        // Declare the body of the shim function:
-        let fcx = new_fn_ctxt(ccx, [], llshimfn, none);
-        let bcx = new_top_block_ctxt(fcx, none);
-        let lltop = bcx.llbb;
-        let llargbundle = llvm::LLVMGetParam(llshimfn, 0 as c_uint);
-        let i = 0u, n = vec::len(tys.arg_tys);
-        let llargvals = [];
-        while i < n {
-            let llargval = load_inbounds(bcx, llargbundle, [0, i as int]);
-            llargvals += [llargval];
-            i += 1u;
-        }
-
-        // Create the call itself and store the return value:
-        let llretval = CallWithConv(bcx, llbasefn,
-                                    llargvals, cc); // r
-        if tys.ret_def {
-            // R** llretptr = &args->r;
-            let llretptr = GEPi(bcx, llargbundle, [0, n as int]);
-            // R* llretloc = *llretptr; /* (args->r) */
-            let llretloc = Load(bcx, llretptr);
-            // *args->r = r;
-            Store(bcx, llretval, llretloc);
-        }
-
-        // Finish up:
-        build_return(bcx);
-        finish_fn(fcx, lltop);
-
-        ret llshimfn;
+        ret build_shim_fn_(ccx, shim_name, llbasefn, tys, cc,
+                           build_args, build_ret);
     }
 
     fn build_wrap_fn(ccx: @crate_ctxt,
@@ -136,30 +209,32 @@ fn trans_native_mod(ccx: @crate_ctxt,
                      num_tps: uint,
                      llshimfn: ValueRef,
                      llwrapfn: ValueRef) {
-        let fcx = new_fn_ctxt(ccx, [], llwrapfn, none);
-        let bcx = new_top_block_ctxt(fcx, none);
-        let lltop = bcx.llbb;
 
-        // Allocate the struct and write the arguments into it.
-        let llargbundle = alloca(bcx, tys.bundle_ty);
-        let i = 0u, n = vec::len(tys.arg_tys);
-        let implicit_args = 2u + num_tps; // ret + env
-        while i < n {
-            let llargval = llvm::LLVMGetParam(llwrapfn,
-                                              (i + implicit_args) as c_uint);
-            store_inbounds(bcx, llargval, llargbundle, [0, i as int]);
-            i += 1u;
+        fn build_args(bcx: @block_ctxt, tys: @c_stack_tys,
+                      llwrapfn: ValueRef, llargbundle: ValueRef,
+                      num_tps: uint) {
+            let i = 0u, n = vec::len(tys.arg_tys);
+            let implicit_args = 2u + num_tps; // ret + env
+            while i < n {
+                let llargval = llvm::LLVMGetParam(
+                    llwrapfn,
+                    (i + implicit_args) as c_uint);
+                store_inbounds(bcx, llargval, llargbundle, [0, i as int]);
+                i += 1u;
+            }
+            let llretptr = llvm::LLVMGetParam(llwrapfn, 0 as c_uint);
+            store_inbounds(bcx, llretptr, llargbundle, [0, n as int]);
         }
-        let llretptr = llvm::LLVMGetParam(llwrapfn, 0 as c_uint);
-        store_inbounds(bcx, llretptr, llargbundle, [0, n as int]);
 
-        // Create call itself.
-        let call_shim_on_c_stack = ccx.upcalls.call_shim_on_c_stack;
-        let llshimfnptr = PointerCast(bcx, llshimfn, T_ptr(T_i8()));
-        let llrawargbundle = PointerCast(bcx, llargbundle, T_ptr(T_i8()));
-        Call(bcx, call_shim_on_c_stack, [llrawargbundle, llshimfnptr]);
-        build_return(bcx);
-        finish_fn(fcx, lltop);
+        fn build_ret(bcx: @block_ctxt, _tys: @c_stack_tys,
+                     _llargbundle: ValueRef) {
+            RetVoid(bcx);
+        }
+
+        build_wrap_fn_(ccx, tys, llshimfn, llwrapfn,
+                       ccx.upcalls.call_shim_on_c_stack,
+                       bind build_args(_, _ ,_ , _, num_tps),
+                       build_ret);
     }
 
     let cc = lib::llvm::CCallConv;
@@ -180,7 +255,7 @@ fn trans_native_mod(ccx: @crate_ctxt,
               build_wrap_fn(ccx, tys, vec::len(tps), llshimfn, llwrapfn);
             }
             none {
-              ccx.sess.span_fatal(
+              ccx.sess.span_bug(
                   native_item.span,
                   "unbound function item in trans_native_mod");
             }
@@ -191,6 +266,96 @@ fn trans_native_mod(ccx: @crate_ctxt,
 }
 
 fn trans_crust_fn(ccx: @crate_ctxt, path: ast_map::path, decl: ast::fn_decl,
-                  body: ast::blk, llfndecl: ValueRef, id: ast::node_id) {
-    trans_fn(ccx, path, decl, body, llfndecl, no_self, [], none, id)
+                  body: ast::blk, llwrapfn: ValueRef, id: ast::node_id) {
+
+    fn build_rust_fn(ccx: @crate_ctxt, path: ast_map::path,
+                     decl: ast::fn_decl, body: ast::blk,
+                     id: ast::node_id) -> ValueRef {
+        let t = ty::node_id_to_type(ccx.tcx, id);
+        let ps = link::mangle_internal_name_by_path(
+            ccx, path + [ast_map::path_name("__rust_abi")]);
+        let llty = type_of_fn_from_ty(ccx, t, []);
+        let llfndecl = decl_internal_cdecl_fn(ccx.llmod, ps, llty);
+        trans_fn(ccx, path, decl, body, llfndecl, no_self, [], none, id);
+        ret llfndecl;
+    }
+
+    fn build_shim_fn(ccx: @crate_ctxt, path: ast_map::path,
+                     llrustfn: ValueRef, tys: @c_stack_tys) -> ValueRef {
+
+        fn build_args(bcx: @block_ctxt, tys: @c_stack_tys,
+                      llargbundle: ValueRef) -> [ValueRef] {
+            let llargvals = [];
+            let i = 0u;
+            let n = vec::len(tys.arg_tys);
+            let llretptr = load_inbounds(bcx, llargbundle, [0, n as int]);
+            llargvals += [llretptr];
+            let llenvptr = C_null(T_opaque_box_ptr(bcx_ccx(bcx)));
+            llargvals += [llenvptr];
+            while i < n {
+                let llargval = load_inbounds(bcx, llargbundle, [0, i as int]);
+                llargvals += [llargval];
+                i += 1u;
+            }
+            ret llargvals;
+        }
+
+        fn build_ret(_bcx: @block_ctxt, _tys: @c_stack_tys,
+                     _llargbundle: ValueRef, _llretval: ValueRef)  {
+            // Nop. The return pointer in the Rust ABI function
+            // is wired directly into the return slot in the shim struct
+        }
+
+        let shim_name = link::mangle_internal_name_by_path(
+            ccx, path + [ast_map::path_name("__rust_stack_shim")]);
+        ret build_shim_fn_(ccx, shim_name, llrustfn, tys,
+                           lib::llvm::CCallConv,
+                           build_args, build_ret);
+    }
+
+    fn build_wrap_fn(ccx: @crate_ctxt, llshimfn: ValueRef,
+                     llwrapfn: ValueRef, tys: @c_stack_tys) {
+
+        fn build_args(bcx: @block_ctxt, tys: @c_stack_tys,
+                      llwrapfn: ValueRef, llargbundle: ValueRef) {
+            let llretptr = alloca(bcx, tys.ret_ty);
+            let i = 0u, n = vec::len(tys.arg_tys);
+            while i < n {
+                let llargval = llvm::LLVMGetParam(
+                    llwrapfn, i as c_uint);
+                store_inbounds(bcx, llargval, llargbundle, [0, i as int]);
+                i += 1u;
+            }
+            store_inbounds(bcx, llretptr, llargbundle, [0, n as int]);
+        }
+
+        fn build_ret(bcx: @block_ctxt, tys: @c_stack_tys,
+                     llargbundle: ValueRef) {
+            let n = vec::len(tys.arg_tys);
+            let llretval = load_inbounds(bcx, llargbundle, [0, n as int]);
+            let llretval = Load(bcx, llretval);
+            Ret(bcx, llretval);
+        }
+
+        build_wrap_fn_(ccx, tys, llshimfn, llwrapfn,
+                       ccx.upcalls.call_shim_on_rust_stack,
+                       build_args, build_ret);
+    }
+
+    let tys = c_stack_tys(ccx, id);
+    // The internal Rust ABI function - runs on the Rust stack
+    let llrustfn = build_rust_fn(ccx, path, decl, body, id);
+    // The internal shim function - runs on the Rust stack
+    let llshimfn = build_shim_fn(ccx, path, llrustfn, tys);
+    // The external C function - runs on the C stack
+    build_wrap_fn(ccx, llshimfn, llwrapfn, tys)
+}
+
+fn register_crust_fn(ccx: @crate_ctxt, sp: span,
+                     path: ast_map::path, node_id: ast::node_id) {
+    let t = ty::node_id_to_type(ccx.tcx, node_id);
+    let (llargtys, llretty, _) = c_arg_and_ret_lltys(ccx, node_id);
+    let llfty = T_fn(llargtys, llretty);
+    register_fn_fuller(ccx, sp, path, "crust fn", node_id,
+                       t, lib::llvm::CCallConv, llfty);
 }
\ No newline at end of file
diff --git a/src/rt/rust_builtin.cpp b/src/rt/rust_builtin.cpp
index 0b4ce047997..918630b18f6 100644
--- a/src/rt/rust_builtin.cpp
+++ b/src/rt/rust_builtin.cpp
@@ -657,8 +657,8 @@ rust_dbg_lock_signal(lock_and_signal *lock) {
 typedef void *(*dbg_callback)(void*);
 
 extern "C" CDECL void *
-rust_dbg_call(dbg_callback *cb, void *data) {
-    return (*cb)(data);
+rust_dbg_call(dbg_callback cb, void *data) {
+    return cb(data);
 }
 
 //
diff --git a/src/test/run-pass/crust-call-deep.rs b/src/test/run-pass/crust-call-deep.rs
new file mode 100644
index 00000000000..230117ee9fc
--- /dev/null
+++ b/src/test/run-pass/crust-call-deep.rs
@@ -0,0 +1,23 @@
+native mod rustrt {
+    fn rust_dbg_call(cb: *u8,
+                     data: ctypes::uintptr_t) -> ctypes::uintptr_t;
+}
+
+crust fn cb(data: ctypes::uintptr_t) -> ctypes::uintptr_t {
+    if data == 1u {
+        data
+    } else {
+        count(data - 1u) + 1u
+    }
+}
+
+fn count(n: uint) -> uint {
+    #debug("n = %?", n);
+    rustrt::rust_dbg_call(cb, n)
+}
+
+fn main() {
+    let result = count(1000u);
+    #debug("result = %?", result);
+    assert result == 1000u;
+}
\ No newline at end of file
diff --git a/src/test/run-pass/crust-call.rs b/src/test/run-pass/crust-call.rs
new file mode 100644
index 00000000000..16ec9f3953c
--- /dev/null
+++ b/src/test/run-pass/crust-call.rs
@@ -0,0 +1,23 @@
+native mod rustrt {
+    fn rust_dbg_call(cb: *u8,
+                     data: ctypes::uintptr_t) -> ctypes::uintptr_t;
+}
+
+crust fn cb(data: ctypes::uintptr_t) -> ctypes::uintptr_t {
+    if data == 1u {
+        data
+    } else {
+        fact(data - 1u) * data
+    }
+}
+
+fn fact(n: uint) -> uint {
+    #debug("n = %?", n);
+    rustrt::rust_dbg_call(cb, n)
+}
+
+fn main() {
+    let result = fact(10u);
+    #debug("result = %?", result);
+    assert result == 3628800u;
+}
\ No newline at end of file