Skip to content

Commit 7dc44ae

Browse files
committed
[AVR] Correctly set the pointer address space when constructing pointers to functions
This patch extends the existing `type_i8p` method so that it requires an explicit address space to be specified. Before this patch, the `type_i8p` method implcitily assumed the default address space, which is not a safe transformation on all targets, namely AVR. The Rust compiler already has support for tracking the "instruction address space" on a per-target basis. This patch extends the code generation routines so that an address space must always be specified. In my estimation, around 15% of the callers of `type_i8p` produced invalid code on AVR due to the loss of address space prior to LLVM final code generation. This would lead to unavoidable assertion errors relating to invalid bitcasts. With this patch, the address space is always either 1) explicitly set to the instruction address space because the logic is dealing with functions which must be placed there, or 2) explicitly set to the default address space 0 because the logic can only operate on data space pointers and thus we keep the existing semantics of assuming the default, "data" address space.
1 parent 1c0a5b0 commit 7dc44ae

File tree

16 files changed

+100
-62
lines changed

16 files changed

+100
-62
lines changed

src/librustc_codegen_llvm/abi.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ impl<'tcx> FnAbiLlvmExt<'tcx> for FnAbi<'tcx, Ty<'tcx>> {
366366
unsafe {
367367
llvm::LLVMPointerType(
368368
self.llvm_type(cx),
369-
cx.data_layout().instruction_address_space as c_uint,
369+
cx.data_layout().instruction_address_space.0 as c_uint,
370370
)
371371
}
372372
}

src/librustc_codegen_llvm/builder.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use rustc_data_structures::small_c_str::SmallCStr;
1818
use rustc_hir::def_id::DefId;
1919
use rustc_middle::ty::layout::TyAndLayout;
2020
use rustc_middle::ty::{self, Ty, TyCtxt};
21-
use rustc_target::abi::{self, Align, Size};
21+
use rustc_target::abi::{self, AddressSpace, Align, Size};
2222
use rustc_target::spec::{HasTargetSpec, Target};
2323
use std::borrow::Cow;
2424
use std::ffi::CStr;
@@ -725,8 +725,8 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
725725
}
726726
let size = self.intcast(size, self.type_isize(), false);
727727
let is_volatile = flags.contains(MemFlags::VOLATILE);
728-
let dst = self.pointercast(dst, self.type_i8p());
729-
let src = self.pointercast(src, self.type_i8p());
728+
let dst = self.pointercast(dst, self.type_i8p(AddressSpace::DATA));
729+
let src = self.pointercast(src, self.type_i8p(AddressSpace::DATA));
730730
unsafe {
731731
llvm::LLVMRustBuildMemCpy(
732732
self.llbuilder,
@@ -758,8 +758,8 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
758758
}
759759
let size = self.intcast(size, self.type_isize(), false);
760760
let is_volatile = flags.contains(MemFlags::VOLATILE);
761-
let dst = self.pointercast(dst, self.type_i8p());
762-
let src = self.pointercast(src, self.type_i8p());
761+
let dst = self.pointercast(dst, self.type_i8p(AddressSpace::DATA));
762+
let src = self.pointercast(src, self.type_i8p(AddressSpace::DATA));
763763
unsafe {
764764
llvm::LLVMRustBuildMemMove(
765765
self.llbuilder,
@@ -782,7 +782,7 @@ impl BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
782782
flags: MemFlags,
783783
) {
784784
let is_volatile = flags.contains(MemFlags::VOLATILE);
785-
let ptr = self.pointercast(ptr, self.type_i8p());
785+
let ptr = self.pointercast(ptr, self.type_i8p(AddressSpace::DATA));
786786
unsafe {
787787
llvm::LLVMRustBuildMemSet(
788788
self.llbuilder,
@@ -1275,7 +1275,7 @@ impl Builder<'a, 'll, 'tcx> {
12751275

12761276
let lifetime_intrinsic = self.cx.get_intrinsic(intrinsic);
12771277

1278-
let ptr = self.pointercast(ptr, self.cx.type_i8p());
1278+
let ptr = self.pointercast(ptr, self.cx.type_i8p(AddressSpace::DATA));
12791279
self.call(lifetime_intrinsic, &[self.cx.const_u64(size), ptr], None);
12801280
}
12811281

src/librustc_codegen_llvm/common.rs

+10-7
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_middle::bug;
1616
use rustc_middle::mir::interpret::{Allocation, GlobalAlloc, Scalar};
1717
use rustc_middle::ty::layout::TyAndLayout;
1818
use rustc_span::symbol::Symbol;
19-
use rustc_target::abi::{self, HasDataLayout, LayoutOf, Pointer, Size};
19+
use rustc_target::abi::{self, AddressSpace, HasDataLayout, LayoutOf, Pointer, Size};
2020

2121
use libc::{c_char, c_uint};
2222
use log::debug;
@@ -244,7 +244,7 @@ impl ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> {
244244
}
245245
}
246246
Scalar::Ptr(ptr) => {
247-
let base_addr = match self.tcx.global_alloc(ptr.alloc_id) {
247+
let (base_addr, base_addr_space) = match self.tcx.global_alloc(ptr.alloc_id) {
248248
GlobalAlloc::Memory(alloc) => {
249249
let init = const_alloc_to_llvm(self, alloc);
250250
let value = match alloc.mutability {
@@ -254,18 +254,21 @@ impl ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> {
254254
if !self.sess().fewer_names() {
255255
llvm::set_value_name(value, format!("{:?}", ptr.alloc_id).as_bytes());
256256
}
257-
value
257+
(value, AddressSpace::DATA)
258258
}
259-
GlobalAlloc::Function(fn_instance) => self.get_fn_addr(fn_instance),
259+
GlobalAlloc::Function(fn_instance) => (
260+
self.get_fn_addr(fn_instance),
261+
self.data_layout().instruction_address_space,
262+
),
260263
GlobalAlloc::Static(def_id) => {
261264
assert!(self.tcx.is_static(def_id));
262265
assert!(!self.tcx.is_thread_local_static(def_id));
263-
self.get_static(def_id)
266+
(self.get_static(def_id), AddressSpace::DATA)
264267
}
265268
};
266269
let llval = unsafe {
267270
llvm::LLVMConstInBoundsGEP(
268-
self.const_bitcast(base_addr, self.type_i8p()),
271+
self.const_bitcast(base_addr, self.type_i8p(base_addr_space)),
269272
&self.const_usize(ptr.offset.bytes()),
270273
1,
271274
)
@@ -296,7 +299,7 @@ impl ConstMethods<'tcx> for CodegenCx<'ll, 'tcx> {
296299

297300
let llval = unsafe {
298301
llvm::LLVMConstInBoundsGEP(
299-
self.const_bitcast(base_addr, self.type_i8p()),
302+
self.const_bitcast(base_addr, self.type_i8p(AddressSpace::DATA)),
300303
&self.const_usize(offset.bytes()),
301304
1,
302305
)

src/librustc_codegen_llvm/consts.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ use rustc_hir::def_id::DefId;
1313
use rustc_hir::Node;
1414
use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
1515
use rustc_middle::mir::interpret::{
16-
read_target_uint, Allocation, ConstValue, ErrorHandled, Pointer,
16+
read_target_uint, Allocation, ConstValue, ErrorHandled, GlobalAlloc, Pointer,
1717
};
1818
use rustc_middle::mir::mono::MonoItem;
1919
use rustc_middle::ty::{self, Instance, Ty};
2020
use rustc_middle::{bug, span_bug};
2121
use rustc_span::symbol::{sym, Symbol};
2222
use rustc_span::Span;
23-
use rustc_target::abi::{Align, HasDataLayout, LayoutOf, Primitive, Scalar, Size};
23+
use rustc_target::abi::{AddressSpace, Align, HasDataLayout, LayoutOf, Primitive, Scalar, Size};
2424

2525
use std::ffi::CStr;
2626

@@ -53,10 +53,16 @@ pub fn const_alloc_to_llvm(cx: &CodegenCx<'ll, '_>, alloc: &Allocation) -> &'ll
5353
)
5454
.expect("const_alloc_to_llvm: could not read relocation pointer")
5555
as u64;
56+
57+
let address_space = match cx.tcx.global_alloc(alloc_id) {
58+
GlobalAlloc::Function(..) => cx.data_layout().instruction_address_space,
59+
GlobalAlloc::Static(..) | GlobalAlloc::Memory(..) => AddressSpace::DATA,
60+
};
61+
5662
llvals.push(cx.scalar_to_backend(
5763
Pointer::new(alloc_id, Size::from_bytes(ptr_offset)).into(),
5864
&Scalar { value: Primitive::Pointer, valid_range: 0..=!0 },
59-
cx.type_i8p(),
65+
cx.type_i8p(address_space),
6066
));
6167
next_offset = offset + pointer_size;
6268
}
@@ -496,7 +502,7 @@ impl StaticMethods for CodegenCx<'ll, 'tcx> {
496502

497503
if attrs.flags.contains(CodegenFnAttrFlags::USED) {
498504
// This static will be stored in the llvm.used variable which is an array of i8*
499-
let cast = llvm::LLVMConstPointerCast(g, self.type_i8p());
505+
let cast = llvm::LLVMConstPointerCast(g, self.type_i8p(AddressSpace::DATA));
500506
self.used_statics.borrow_mut().push(cast);
501507
}
502508
}

src/librustc_codegen_llvm/context.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ use rustc_session::config::{CFGuard, CrateType, DebugInfo};
2020
use rustc_session::Session;
2121
use rustc_span::source_map::{Span, DUMMY_SP};
2222
use rustc_span::symbol::Symbol;
23-
use rustc_target::abi::{HasDataLayout, LayoutOf, PointeeInfo, Size, TargetDataLayout, VariantIdx};
23+
use rustc_target::abi::{
24+
AddressSpace, HasDataLayout, LayoutOf, PointeeInfo, Size, TargetDataLayout, VariantIdx,
25+
};
2426
use rustc_target::spec::{HasTargetSpec, RelocModel, Target, TlsModel};
2527

2628
use std::cell::{Cell, RefCell};
@@ -452,7 +454,7 @@ impl CodegenCx<'b, 'tcx> {
452454
($($field_ty:expr),*) => (self.type_struct( &[$($field_ty),*], false))
453455
}
454456

455-
let i8p = self.type_i8p();
457+
let i8p = self.type_i8p(AddressSpace::DATA);
456458
let void = self.type_void();
457459
let i1 = self.type_i1();
458460
let t_i8 = self.type_i8();

src/librustc_codegen_llvm/intrinsic.rs

+11-8
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use rustc_middle::ty::layout::{FnAbiExt, HasTyCtxt};
2323
use rustc_middle::ty::{self, Ty};
2424
use rustc_middle::{bug, span_bug};
2525
use rustc_span::Span;
26-
use rustc_target::abi::{self, HasDataLayout, LayoutOf, Primitive};
26+
use rustc_target::abi::{self, AddressSpace, HasDataLayout, LayoutOf, Primitive};
2727
use rustc_target::spec::PanicStrategy;
2828

2929
use std::cmp::Ordering;
@@ -930,7 +930,7 @@ fn codegen_msvc_try(
930930
//
931931
// More information can be found in libstd's seh.rs implementation.
932932
let ptr_align = bx.tcx().data_layout.pointer_align.abi;
933-
let slot = bx.alloca(bx.type_i8p(), ptr_align);
933+
let slot = bx.alloca(bx.type_i8p(AddressSpace::DATA), ptr_align);
934934
bx.invoke(try_func, &[data], normal.llbb(), catchswitch.llbb(), None);
935935

936936
normal.ret(bx.const_i32(0));
@@ -952,10 +952,13 @@ fn codegen_msvc_try(
952952
//
953953
// When modifying, make sure that the type_name string exactly matches
954954
// the one used in src/libpanic_unwind/seh.rs.
955-
let type_info_vtable = bx.declare_global("??_7type_info@@6B@", bx.type_i8p());
955+
let type_info_vtable =
956+
bx.declare_global("??_7type_info@@6B@", bx.type_i8p(AddressSpace::DATA));
956957
let type_name = bx.const_bytes(b"rust_panic\0");
957-
let type_info =
958-
bx.const_struct(&[type_info_vtable, bx.const_null(bx.type_i8p()), type_name], false);
958+
let type_info = bx.const_struct(
959+
&[type_info_vtable, bx.const_null(bx.type_i8p(AddressSpace::DATA)), type_name],
960+
false,
961+
);
959962
let tydesc = bx.declare_global("__rust_panic_type_info", bx.val_ty(type_info));
960963
unsafe {
961964
llvm::LLVMRustSetLinkage(tydesc, llvm::Linkage::LinkOnceODRLinkage);
@@ -1035,14 +1038,14 @@ fn codegen_gnu_try(
10351038
// being thrown. The second value is a "selector" indicating which of
10361039
// the landing pad clauses the exception's type had been matched to.
10371040
// rust_try ignores the selector.
1038-
let lpad_ty = bx.type_struct(&[bx.type_i8p(), bx.type_i32()], false);
1041+
let lpad_ty = bx.type_struct(&[bx.type_i8p(AddressSpace::DATA), bx.type_i32()], false);
10391042
let vals = catch.landing_pad(lpad_ty, bx.eh_personality(), 1);
10401043
let tydesc = match bx.tcx().lang_items().eh_catch_typeinfo() {
10411044
Some(tydesc) => {
10421045
let tydesc = bx.get_static(tydesc);
1043-
bx.bitcast(tydesc, bx.type_i8p())
1046+
bx.bitcast(tydesc, bx.type_i8p(AddressSpace::DATA))
10441047
}
1045-
None => bx.const_null(bx.type_i8p()),
1048+
None => bx.const_null(bx.type_i8p(AddressSpace::DATA)),
10461049
};
10471050
catch.add_clause(vals, tydesc);
10481051
let ptr = catch.extract_value(vals, 0);

src/librustc_codegen_llvm/llvm/ffi.rs

-6
Original file line numberDiff line numberDiff line change
@@ -1334,12 +1334,6 @@ extern "C" {
13341334
DestTy: &'a Type,
13351335
Name: *const c_char,
13361336
) -> &'a Value;
1337-
pub fn LLVMBuildPointerCast(
1338-
B: &Builder<'a>,
1339-
Val: &'a Value,
1340-
DestTy: &'a Type,
1341-
Name: *const c_char,
1342-
) -> &'a Value;
13431337
pub fn LLVMRustBuildIntCast(
13441338
B: &Builder<'a>,
13451339
Val: &'a Value,

src/librustc_codegen_llvm/type_.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc_middle::bug;
1515
use rustc_middle::ty::layout::TyAndLayout;
1616
use rustc_middle::ty::Ty;
1717
use rustc_target::abi::call::{CastTarget, FnAbi, Reg};
18-
use rustc_target::abi::{Align, Integer, Size};
18+
use rustc_target::abi::{AddressSpace, Align, Integer, Size};
1919

2020
use std::fmt;
2121
use std::ptr;
@@ -198,9 +198,13 @@ impl BaseTypeMethods<'tcx> for CodegenCx<'ll, 'tcx> {
198198
assert_ne!(
199199
self.type_kind(ty),
200200
TypeKind::Function,
201-
"don't call ptr_to on function types, use ptr_to_llvm_type on FnAbi instead"
201+
"don't call ptr_to on function types, use ptr_to_llvm_type on FnAbi instead or explicitly specify an address space if it makes sense"
202202
);
203-
ty.ptr_to()
203+
ty.ptr_to(AddressSpace::DATA)
204+
}
205+
206+
fn type_ptr_to_ext(&self, ty: &'ll Type, address_space: AddressSpace) -> &'ll Type {
207+
ty.ptr_to(address_space)
204208
}
205209

206210
fn element_type(&self, ty: &'ll Type) -> &'ll Type {
@@ -241,11 +245,11 @@ impl Type {
241245
}
242246

243247
pub fn i8p_llcx(llcx: &llvm::Context) -> &Type {
244-
Type::i8_llcx(llcx).ptr_to()
248+
Type::i8_llcx(llcx).ptr_to(AddressSpace::DATA)
245249
}
246250

247-
fn ptr_to(&self) -> &Type {
248-
unsafe { llvm::LLVMPointerType(&self, 0) }
251+
fn ptr_to(&self, address_space: AddressSpace) -> &Type {
252+
unsafe { llvm::LLVMPointerType(&self, address_space.0) }
249253
}
250254
}
251255

src/librustc_codegen_llvm/va_arg.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_codegen_ssa::traits::{
88
};
99
use rustc_middle::ty::layout::HasTyCtxt;
1010
use rustc_middle::ty::Ty;
11-
use rustc_target::abi::{Align, HasDataLayout, LayoutOf, Size};
11+
use rustc_target::abi::{AddressSpace, Align, HasDataLayout, LayoutOf, Size};
1212

1313
#[allow(dead_code)]
1414
fn round_pointer_up_to_alignment(
@@ -32,7 +32,7 @@ fn emit_direct_ptr_va_arg(
3232
slot_size: Align,
3333
allow_higher_align: bool,
3434
) -> (&'ll Value, Align) {
35-
let va_list_ptr_ty = bx.cx().type_ptr_to(bx.cx.type_i8p());
35+
let va_list_ptr_ty = bx.cx().type_ptr_to(bx.cx.type_i8p(AddressSpace::DATA));
3636
let va_list_addr = if list.layout.llvm_type(bx.cx) != va_list_ptr_ty {
3737
bx.bitcast(list.immediate(), va_list_ptr_ty)
3838
} else {
@@ -42,7 +42,7 @@ fn emit_direct_ptr_va_arg(
4242
let ptr = bx.load(va_list_addr, bx.tcx().data_layout.pointer_align.abi);
4343

4444
let (addr, addr_align) = if allow_higher_align && align > slot_size {
45-
(round_pointer_up_to_alignment(bx, ptr, align, bx.cx().type_i8p()), align)
45+
(round_pointer_up_to_alignment(bx, ptr, align, bx.cx().type_i8p(AddressSpace::DATA)), align)
4646
} else {
4747
(ptr, slot_size)
4848
};

src/librustc_codegen_ssa/base.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ use rustc_session::utils::NativeLibKind;
4848
use rustc_session::Session;
4949
use rustc_span::Span;
5050
use rustc_symbol_mangling::test as symbol_names_test;
51-
use rustc_target::abi::{Abi, Align, LayoutOf, Scalar, VariantIdx};
51+
use rustc_target::abi::{Abi, AddressSpace, Align, LayoutOf, Scalar, VariantIdx};
5252

5353
use std::cmp;
5454
use std::ops::{Deref, DerefMut};
@@ -423,7 +423,10 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
423423
// The entry function is either `int main(void)` or `int main(int argc, char **argv)`,
424424
// depending on whether the target needs `argc` and `argv` to be passed in.
425425
let llfty = if cx.sess().target.target.options.main_needs_argc_argv {
426-
cx.type_func(&[cx.type_int(), cx.type_ptr_to(cx.type_i8p())], cx.type_int())
426+
cx.type_func(
427+
&[cx.type_int(), cx.type_ptr_to(cx.type_i8p(AddressSpace::DATA))],
428+
cx.type_int(),
429+
)
427430
} else {
428431
cx.type_func(&[], cx.type_int())
429432
};
@@ -471,7 +474,11 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
471474
);
472475
(
473476
start_fn,
474-
vec![bx.pointercast(rust_main, cx.type_ptr_to(cx.type_i8p())), arg_argc, arg_argv],
477+
vec![
478+
bx.pointercast(rust_main, cx.type_ptr_to(cx.type_i8p(AddressSpace::DATA))),
479+
arg_argc,
480+
arg_argv,
481+
],
475482
)
476483
} else {
477484
debug!("using user-defined start fn");
@@ -501,7 +508,7 @@ fn get_argc_argv<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
501508
} else {
502509
// The Rust start function doesn't need `argc` and `argv`, so just pass zeros.
503510
let arg_argc = bx.const_int(cx.type_int(), 0);
504-
let arg_argv = bx.const_null(cx.type_ptr_to(cx.type_i8p()));
511+
let arg_argv = bx.const_null(cx.type_ptr_to(cx.type_i8p(AddressSpace::DATA)));
505512
(arg_argc, arg_argv)
506513
}
507514
}

src/librustc_codegen_ssa/meth.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub fn get_vtable<'tcx, Cx: CodegenMethods<'tcx>>(
7575
}
7676

7777
// Not in the cache; build it.
78-
let nullptr = cx.const_null(cx.type_i8p());
78+
let nullptr = cx.const_null(cx.type_i8p(cx.data_layout().instruction_address_space));
7979

8080
let methods_root;
8181
let methods = if let Some(trait_ref) = trait_ref {

src/librustc_codegen_ssa/mir/block.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use rustc_middle::ty::layout::{FnAbiExt, HasTyCtxt};
1919
use rustc_middle::ty::{self, Instance, Ty, TypeFoldable};
2020
use rustc_span::{source_map::Span, symbol::Symbol};
2121
use rustc_target::abi::call::{ArgAbi, FnAbi, PassMode};
22-
use rustc_target::abi::{self, LayoutOf};
22+
use rustc_target::abi::{self, AddressSpace, LayoutOf};
2323
use rustc_target::spec::abi::Abi;
2424

2525
use std::borrow::Cow;
@@ -1247,7 +1247,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
12471247

12481248
fn landing_pad_type(&self) -> Bx::Type {
12491249
let cx = self.cx;
1250-
cx.type_struct(&[cx.type_i8p(), cx.type_i32()], false)
1250+
cx.type_struct(&[cx.type_i8p(AddressSpace::DATA), cx.type_i32()], false)
12511251
}
12521252

12531253
fn unreachable_block(&mut self) -> Bx::BasicBlock {

src/librustc_codegen_ssa/mir/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use rustc_middle::mir::interpret::ErrorHandled;
66
use rustc_middle::ty::layout::{FnAbiExt, HasTyCtxt, TyAndLayout};
77
use rustc_middle::ty::{self, Instance, Ty, TypeFoldable};
88
use rustc_target::abi::call::{FnAbi, PassMode};
9+
use rustc_target::abi::HasDataLayout;
910

1011
use std::iter;
1112

@@ -323,7 +324,8 @@ fn create_funclets<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
323324
// C++ personality function, but `catch (...)` has no type so
324325
// it's null. The 64 here is actually a bitfield which
325326
// represents that this is a catch-all block.
326-
let null = bx.const_null(bx.type_i8p());
327+
let null =
328+
bx.const_null(bx.type_i8p(bx.cx().data_layout().instruction_address_space));
327329
let sixty_four = bx.const_i32(64);
328330
funclet = cp_bx.catch_pad(cs, &[null, sixty_four, null]);
329331
cp_bx.br(llbb);

0 commit comments

Comments
 (0)