about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLuqman Aden <me@luqman.ca>2016-06-02 21:32:07 -0400
committerEduard Burtescu <edy.burt@gmail.com>2016-06-05 14:46:33 +0300
commitda081e1eac64d981209a742f8edea1a55127ce42 (patch)
tree06960cbf49f2d22372235bdf9504272146d4ad15
parente71f6d8ac98a9db4fe58448fc70582339bc97ccd (diff)
downloadrust-da081e1eac64d981209a742f8edea1a55127ce42.tar.gz
rust-da081e1eac64d981209a742f8edea1a55127ce42.zip
[MIR] Handle call return values that need to be casted properly.
-rw-r--r--src/librustc_trans/mir/block.rs44
-rw-r--r--src/test/run-pass/mir_cast_fn_ret.rs24
2 files changed, 65 insertions, 3 deletions
diff --git a/src/librustc_trans/mir/block.rs b/src/librustc_trans/mir/block.rs
index b404475b584..c8f9d46f749 100644
--- a/src/librustc_trans/mir/block.rs
+++ b/src/librustc_trans/mir/block.rs
@@ -19,11 +19,11 @@ use base;
 use build;
 use callee::{Callee, CalleeData, Fn, Intrinsic, NamedTupleConstructor, Virtual};
 use common::{self, Block, BlockAndBuilder, LandingPad};
-use common::{C_bool, C_str_slice, C_struct, C_u32, C_undef};
+use common::{C_bool, C_str_slice, C_struct, C_u32, C_uint, C_undef};
 use consts;
 use debuginfo::DebugLoc;
 use Disr;
-use machine::{llalign_of_min, llbitsize_of_real};
+use machine::{llalign_of_min, llbitsize_of_real, llsize_of_store};
 use meth;
 use type_of;
 use glue;
@@ -39,6 +39,8 @@ use super::lvalue::{LvalueRef, load_fat_ptr};
 use super::operand::OperandRef;
 use super::operand::OperandValue::*;
 
+use std::cmp;
+
 impl<'bcx, 'tcx> MirContext<'bcx, 'tcx> {
     pub fn trans_block(&mut self, bb: mir::BasicBlock) {
         let mut bcx = self.bcx(bb);
@@ -852,7 +854,43 @@ impl<'bcx, 'tcx> MirContext<'bcx, 'tcx> {
 
         match dest {
             Nothing => (),
-            Store(dst) => ret_ty.store(bcx, op.immediate(), dst),
+            Store(dst) => {
+                if let Some(llcast_ty) = ret_ty.cast {
+                    let ccx = bcx.ccx();
+                    // The actual return type is a struct, but the ABI
+                    // adaptation code has cast it into some scalar type.  The
+                    // code that follows is the only reliable way I have
+                    // found to do a transform like i64 -> {i32,i32}.
+                    // Basically we dump the data onto the stack then memcpy it.
+                    //
+                    // Other approaches I tried:
+                    // - Casting rust ret pointer to the foreign type and using Store
+                    //   is (a) unsafe if size of foreign type > size of rust type and
+                    //   (b) runs afoul of strict aliasing rules, yielding invalid
+                    //   assembly under -O (specifically, the store gets removed).
+                    // - Truncating foreign type to correct integral type and then
+                    //   bitcasting to the struct type yields invalid cast errors.
+
+                    // We instead thus allocate some scratch space...
+                    let llscratch = bcx.alloca(llcast_ty, "fn_ret_cast");
+                    bcx.with_block(|bcx| base::call_lifetime_start(bcx, llscratch));
+
+                    // ...where we first store the value...
+                    bcx.store(op.immediate(), llscratch);
+
+                    // ...and then memcpy it to the intended destination.
+                    base::call_memcpy(bcx,
+                                      bcx.pointercast(dst, Type::i8p(ccx)),
+                                      bcx.pointercast(llscratch, Type::i8p(ccx)),
+                                      C_uint(ccx, llsize_of_store(ccx, ret_ty.original_ty)),
+                                      cmp::min(llalign_of_min(ccx, ret_ty.original_ty),
+                                               llalign_of_min(ccx, llcast_ty)) as u32);
+
+                    bcx.with_block(|bcx| base::call_lifetime_end(bcx, llscratch));
+                } else {
+                    ret_ty.store(bcx, op.immediate(), dst);
+                }
+            }
             IndirectOperand(tmp, idx) => {
                 let op = self.trans_load(bcx, tmp, op.ty);
                 self.temps[idx as usize] = TempRef::Operand(Some(op));
diff --git a/src/test/run-pass/mir_cast_fn_ret.rs b/src/test/run-pass/mir_cast_fn_ret.rs
new file mode 100644
index 00000000000..5bdc14f659c
--- /dev/null
+++ b/src/test/run-pass/mir_cast_fn_ret.rs
@@ -0,0 +1,24 @@
+// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(rustc_attrs)]
+
+pub extern "C" fn foo() -> (u8, u8, u8) {
+    (1, 2, 3)
+}
+
+#[rustc_mir]
+pub fn bar() -> u8 {
+    foo().2
+}
+
+fn main() {
+    assert_eq!(bar(), 3);
+}