Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
int
Rust
提交
96cf7999
R
Rust
项目概览
int
/
Rust
大约 1 年 前同步成功
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
Rust
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
96cf7999
编写于
2月 18, 2022
作者:
B
bjorn3
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Introduce Bx::switch_to_block
上级
0edcf1e2
变更
6
隐藏空白更改
内联
并排
Showing
6 changed file
with
95 addition
and
94 deletion
+95
-94
compiler/rustc_codegen_gcc/src/builder.rs
compiler/rustc_codegen_gcc/src/builder.rs
+15
-9
compiler/rustc_codegen_llvm/src/builder.rs
compiler/rustc_codegen_llvm/src/builder.rs
+4
-0
compiler/rustc_codegen_llvm/src/intrinsic.rs
compiler/rustc_codegen_llvm/src/intrinsic.rs
+44
-50
compiler/rustc_codegen_llvm/src/va_arg.rs
compiler/rustc_codegen_llvm/src/va_arg.rs
+21
-25
compiler/rustc_codegen_ssa/src/mir/block.rs
compiler/rustc_codegen_ssa/src/mir/block.rs
+9
-10
compiler/rustc_codegen_ssa/src/traits/builder.rs
compiler/rustc_codegen_ssa/src/traits/builder.rs
+2
-0
未找到文件。
compiler/rustc_codegen_gcc/src/builder.rs
浏览文件 @
96cf7999
...
...
@@ -404,6 +404,11 @@ fn append_sibling_block(&mut self, name: &str) -> Block<'gcc> {
func
.new_block
(
name
)
}
fn
switch_to_block
(
&
mut
self
,
block
:
Self
::
BasicBlock
)
{
*
self
.cx.current_block
.borrow_mut
()
=
Some
(
block
);
self
.block
=
Some
(
block
);
}
fn
ret_void
(
&
mut
self
)
{
self
.llbb
()
.end_with_void_return
(
None
)
}
...
...
@@ -886,19 +891,20 @@ fn write_operand_repeatedly(mut self, cg_elem: OperandRef<'tcx, RValue<'gcc>>, c
self
.br
(
header_bb
);
let
mut
header_bx
=
Builder
::
build
(
self
.cx
,
header_bb
);
let
keep_going
=
header_bx
.icmp
(
IntPredicate
::
IntNE
,
current_val
,
end
);
header_bx
.cond_br
(
keep_going
,
body_bb
,
next_bb
);
self
.switch_to_block
(
header_bb
);
let
keep_going
=
self
.icmp
(
IntPredicate
::
IntNE
,
current_val
,
end
);
self
.cond_br
(
keep_going
,
body_bb
,
next_bb
);
let
mut
body_bx
=
Builder
::
build
(
self
.cx
,
body_bb
);
self
.switch_to_block
(
body_bb
);
let
align
=
dest
.align
.restrict_for_offset
(
dest
.layout
.field
(
self
.cx
(),
0
)
.size
);
cg_elem
.val
.store
(
&
mut
body_bx
,
PlaceRef
::
new_sized_aligned
(
current_val
,
cg_elem
.layout
,
align
));
cg_elem
.val
.store
(
&
mut
self
,
PlaceRef
::
new_sized_aligned
(
current_val
,
cg_elem
.layout
,
align
));
let
next
=
body_bx
.inbounds_gep
(
self
.backend_type
(
cg_elem
.layout
),
current
.to_rvalue
(),
&
[
self
.const_usize
(
1
)]);
body_bx
.llbb
()
.add_assignment
(
None
,
current
,
next
);
body_bx
.br
(
header_bb
);
let
next
=
self
.inbounds_gep
(
self
.backend_type
(
cg_elem
.layout
),
current
.to_rvalue
(),
&
[
self
.const_usize
(
1
)]);
self
.llbb
()
.add_assignment
(
None
,
current
,
next
);
self
.br
(
header_bb
);
Builder
::
build
(
self
.cx
,
next_bb
)
self
.switch_to_block
(
next_bb
);
self
}
fn
range_metadata
(
&
mut
self
,
_
load
:
RValue
<
'gcc
>
,
_
range
:
WrappingRange
)
{
...
...
compiler/rustc_codegen_llvm/src/builder.rs
浏览文件 @
96cf7999
...
...
@@ -166,6 +166,10 @@ fn append_sibling_block(&mut self, name: &str) -> &'ll BasicBlock {
Self
::
append_block
(
self
.cx
,
self
.llfn
(),
name
)
}
fn
switch_to_block
(
&
mut
self
,
llbb
:
Self
::
BasicBlock
)
{
*
self
=
Self
::
build
(
self
.cx
,
llbb
)
}
fn
ret_void
(
&
mut
self
)
{
unsafe
{
llvm
::
LLVMBuildRetVoid
(
self
.llbuilder
);
...
...
compiler/rustc_codegen_llvm/src/intrinsic.rs
浏览文件 @
96cf7999
...
...
@@ -522,11 +522,11 @@ fn codegen_msvc_try<'ll>(
let
try_func_ty
=
bx
.type_func
(
&
[
bx
.type_i8p
()],
bx
.type_void
());
bx
.invoke
(
try_func_ty
,
try_func
,
&
[
data
],
normal
,
catchswitch
,
None
);
let
mut
normal
=
Builder
::
build
(
bx
.cx
,
normal
);
normal
.ret
(
bx
.const_i32
(
0
));
bx
.switch_to_block
(
normal
);
bx
.ret
(
bx
.const_i32
(
0
));
let
mut
catchswitch
=
Builder
::
build
(
bx
.cx
,
catchswitch
);
let
cs
=
catchswitch
.catch_switch
(
None
,
None
,
&
[
catchpad_rust
,
catchpad_foreign
]);
bx
.switch_to_block
(
catchswitch
);
let
cs
=
bx
.catch_switch
(
None
,
None
,
&
[
catchpad_rust
,
catchpad_foreign
]);
// We can't use the TypeDescriptor defined in libpanic_unwind because it
// might be in another DLL and the SEH encoding only supports specifying
...
...
@@ -559,24 +559,24 @@ fn codegen_msvc_try<'ll>(
// since our exception object effectively contains a Box.
//
// Source: MicrosoftCXXABI::getAddrOfCXXCatchHandlerType in clang
let
mut
catchpad_rust
=
Builder
::
build
(
bx
.cx
,
catchpad_rust
);
bx
.switch_to_block
(
catchpad_rust
);
let
flags
=
bx
.const_i32
(
8
);
let
funclet
=
catchpad_rust
.catch_pad
(
cs
,
&
[
tydesc
,
flags
,
slot
]);
let
ptr
=
catchpad_rust
.load
(
bx
.type_i8p
(),
slot
,
ptr_align
);
let
funclet
=
bx
.catch_pad
(
cs
,
&
[
tydesc
,
flags
,
slot
]);
let
ptr
=
bx
.load
(
bx
.type_i8p
(),
slot
,
ptr_align
);
let
catch_ty
=
bx
.type_func
(
&
[
bx
.type_i8p
(),
bx
.type_i8p
()],
bx
.type_void
());
catchpad_rust
.call
(
catch_ty
,
catch_func
,
&
[
data
,
ptr
],
Some
(
&
funclet
));
catchpad_rust
.catch_ret
(
&
funclet
,
caught
);
bx
.call
(
catch_ty
,
catch_func
,
&
[
data
,
ptr
],
Some
(
&
funclet
));
bx
.catch_ret
(
&
funclet
,
caught
);
// The flag value of 64 indicates a "catch-all".
let
mut
catchpad_foreign
=
Builder
::
build
(
bx
.cx
,
catchpad_foreign
);
bx
.switch_to_block
(
catchpad_foreign
);
let
flags
=
bx
.const_i32
(
64
);
let
null
=
bx
.const_null
(
bx
.type_i8p
());
let
funclet
=
catchpad_foreign
.catch_pad
(
cs
,
&
[
null
,
flags
,
null
]);
catchpad_foreign
.call
(
catch_ty
,
catch_func
,
&
[
data
,
null
],
Some
(
&
funclet
));
catchpad_foreign
.catch_ret
(
&
funclet
,
caught
);
let
funclet
=
bx
.catch_pad
(
cs
,
&
[
null
,
flags
,
null
]);
bx
.call
(
catch_ty
,
catch_func
,
&
[
data
,
null
],
Some
(
&
funclet
));
bx
.catch_ret
(
&
funclet
,
caught
);
let
mut
caught
=
Builder
::
build
(
bx
.cx
,
caught
);
caught
.ret
(
bx
.const_i32
(
1
));
bx
.switch_to_block
(
caught
);
bx
.ret
(
bx
.const_i32
(
1
));
});
// Note that no invoke is used here because by definition this function
...
...
@@ -626,8 +626,8 @@ fn codegen_gnu_try<'ll>(
let
try_func_ty
=
bx
.type_func
(
&
[
bx
.type_i8p
()],
bx
.type_void
());
bx
.invoke
(
try_func_ty
,
try_func
,
&
[
data
],
then
,
catch
,
None
);
let
mut
then
=
Builder
::
build
(
bx
.cx
,
then
);
then
.ret
(
bx
.const_i32
(
0
));
bx
.switch_to_block
(
then
);
bx
.ret
(
bx
.const_i32
(
0
));
// Type indicator for the exception being thrown.
//
...
...
@@ -635,15 +635,15 @@ fn codegen_gnu_try<'ll>(
// being thrown. The second value is a "selector" indicating which of
// the landing pad clauses the exception's type had been matched to.
// rust_try ignores the selector.
let
mut
catch
=
Builder
::
build
(
bx
.cx
,
catch
);
bx
.switch_to_block
(
catch
);
let
lpad_ty
=
bx
.type_struct
(
&
[
bx
.type_i8p
(),
bx
.type_i32
()],
false
);
let
vals
=
catch
.landing_pad
(
lpad_ty
,
bx
.eh_personality
(),
1
);
let
vals
=
bx
.landing_pad
(
lpad_ty
,
bx
.eh_personality
(),
1
);
let
tydesc
=
bx
.const_null
(
bx
.type_i8p
());
catch
.add_clause
(
vals
,
tydesc
);
let
ptr
=
catch
.extract_value
(
vals
,
0
);
bx
.add_clause
(
vals
,
tydesc
);
let
ptr
=
bx
.extract_value
(
vals
,
0
);
let
catch_ty
=
bx
.type_func
(
&
[
bx
.type_i8p
(),
bx
.type_i8p
()],
bx
.type_void
());
catch
.call
(
catch_ty
,
catch_func
,
&
[
data
,
ptr
],
None
);
catch
.ret
(
bx
.const_i32
(
1
));
bx
.call
(
catch_ty
,
catch_func
,
&
[
data
,
ptr
],
None
);
bx
.ret
(
bx
.const_i32
(
1
));
});
// Note that no invoke is used here because by definition this function
...
...
@@ -690,51 +690,45 @@ fn codegen_emcc_try<'ll>(
let
try_func_ty
=
bx
.type_func
(
&
[
bx
.type_i8p
()],
bx
.type_void
());
bx
.invoke
(
try_func_ty
,
try_func
,
&
[
data
],
then
,
catch
,
None
);
let
mut
then
=
Builder
::
build
(
bx
.cx
,
then
);
then
.ret
(
bx
.const_i32
(
0
));
bx
.switch_to_block
(
then
);
bx
.ret
(
bx
.const_i32
(
0
));
// Type indicator for the exception being thrown.
//
// The first value in this tuple is a pointer to the exception object
// being thrown. The second value is a "selector" indicating which of
// the landing pad clauses the exception's type had been matched to.
let
mut
catch
=
Builder
::
build
(
bx
.cx
,
catch
);
bx
.switch_to_block
(
catch
);
let
tydesc
=
bx
.eh_catch_typeinfo
();
let
lpad_ty
=
bx
.type_struct
(
&
[
bx
.type_i8p
(),
bx
.type_i32
()],
false
);
let
vals
=
catch
.landing_pad
(
lpad_ty
,
bx
.eh_personality
(),
2
);
catch
.add_clause
(
vals
,
tydesc
);
catch
.add_clause
(
vals
,
bx
.const_null
(
bx
.type_i8p
()));
let
ptr
=
catch
.extract_value
(
vals
,
0
);
let
selector
=
catch
.extract_value
(
vals
,
1
);
let
vals
=
bx
.landing_pad
(
lpad_ty
,
bx
.eh_personality
(),
2
);
bx
.add_clause
(
vals
,
tydesc
);
bx
.add_clause
(
vals
,
bx
.const_null
(
bx
.type_i8p
()));
let
ptr
=
bx
.extract_value
(
vals
,
0
);
let
selector
=
bx
.extract_value
(
vals
,
1
);
// Check if the typeid we got is the one for a Rust panic.
let
rust_typeid
=
catch
.call_intrinsic
(
"llvm.eh.typeid.for"
,
&
[
tydesc
]);
let
is_rust_panic
=
catch
.icmp
(
IntPredicate
::
IntEQ
,
selector
,
rust_typeid
);
let
is_rust_panic
=
catch
.zext
(
is_rust_panic
,
bx
.type_bool
());
let
rust_typeid
=
bx
.call_intrinsic
(
"llvm.eh.typeid.for"
,
&
[
tydesc
]);
let
is_rust_panic
=
bx
.icmp
(
IntPredicate
::
IntEQ
,
selector
,
rust_typeid
);
let
is_rust_panic
=
bx
.zext
(
is_rust_panic
,
bx
.type_bool
());
// We need to pass two values to catch_func (ptr and is_rust_panic), so
// create an alloca and pass a pointer to that.
let
ptr_align
=
bx
.tcx
()
.data_layout.pointer_align.abi
;
let
i8_align
=
bx
.tcx
()
.data_layout.i8_align.abi
;
let
catch_data_type
=
bx
.type_struct
(
&
[
bx
.type_i8p
(),
bx
.type_bool
()],
false
);
let
catch_data
=
catch
.alloca
(
catch_data_type
,
ptr_align
);
let
catch_data_0
=
catch
.inbounds_gep
(
catch_data_type
,
catch_data
,
&
[
bx
.const_usize
(
0
),
bx
.const_usize
(
0
)],
);
catch
.store
(
ptr
,
catch_data_0
,
ptr_align
);
let
catch_data_1
=
catch
.inbounds_gep
(
catch_data_type
,
catch_data
,
&
[
bx
.const_usize
(
0
),
bx
.const_usize
(
1
)],
);
catch
.store
(
is_rust_panic
,
catch_data_1
,
i8_align
);
let
catch_data
=
catch
.bitcast
(
catch_data
,
bx
.type_i8p
());
let
catch_data
=
bx
.alloca
(
catch_data_type
,
ptr_align
);
let
catch_data_0
=
bx
.inbounds_gep
(
catch_data_type
,
catch_data
,
&
[
bx
.const_usize
(
0
),
bx
.const_usize
(
0
)]);
bx
.store
(
ptr
,
catch_data_0
,
ptr_align
);
let
catch_data_1
=
bx
.inbounds_gep
(
catch_data_type
,
catch_data
,
&
[
bx
.const_usize
(
0
),
bx
.const_usize
(
1
)]);
bx
.store
(
is_rust_panic
,
catch_data_1
,
i8_align
);
let
catch_data
=
bx
.bitcast
(
catch_data
,
bx
.type_i8p
());
let
catch_ty
=
bx
.type_func
(
&
[
bx
.type_i8p
(),
bx
.type_i8p
()],
bx
.type_void
());
catch
.call
(
catch_ty
,
catch_func
,
&
[
data
,
catch_data
],
None
);
catch
.ret
(
bx
.const_i32
(
1
));
bx
.call
(
catch_ty
,
catch_func
,
&
[
data
,
catch_data
],
None
);
bx
.ret
(
bx
.const_i32
(
1
));
});
// Note that no invoke is used here because by definition this function
...
...
compiler/rustc_codegen_llvm/src/va_arg.rs
浏览文件 @
96cf7999
...
...
@@ -131,51 +131,47 @@ fn emit_aapcs_va_arg<'ll, 'tcx>(
// it could be on the stack so we have to update the offset and then check
// the offset again.
let
mut
maybe_reg
=
Builder
::
build
(
bx
.cx
,
maybe_reg
);
bx
.switch_to_block
(
maybe_reg
);
if
gr_type
&&
layout
.align.abi
.bytes
()
>
8
{
reg_off_v
=
maybe_reg
.add
(
reg_off_v
,
bx
.const_i32
(
15
));
reg_off_v
=
maybe_reg
.and
(
reg_off_v
,
bx
.const_i32
(
-
16
));
reg_off_v
=
bx
.add
(
reg_off_v
,
bx
.const_i32
(
15
));
reg_off_v
=
bx
.and
(
reg_off_v
,
bx
.const_i32
(
-
16
));
}
let
new_reg_off_v
=
maybe_reg
.add
(
reg_off_v
,
bx
.const_i32
(
slot_size
as
i32
));
let
new_reg_off_v
=
bx
.add
(
reg_off_v
,
bx
.const_i32
(
slot_size
as
i32
));
maybe_reg
.store
(
new_reg_off_v
,
reg_off
,
offset_align
);
bx
.store
(
new_reg_off_v
,
reg_off
,
offset_align
);
// Check to see if we have overflowed the registers as a result of this.
// If we have then we need to use the stack for this value
let
use_stack
=
maybe_reg
.icmp
(
IntPredicate
::
IntSGT
,
new_reg_off_v
,
zero
);
maybe_reg
.cond_br
(
use_stack
,
on_stack
,
in_reg
);
let
use_stack
=
bx
.icmp
(
IntPredicate
::
IntSGT
,
new_reg_off_v
,
zero
);
bx
.cond_br
(
use_stack
,
on_stack
,
in_reg
);
let
mut
in_reg
=
Builder
::
build
(
bx
.cx
,
in_reg
);
bx
.switch_to_block
(
in_reg
);
let
top_type
=
bx
.type_i8p
();
let
top
=
in_reg
.struct_gep
(
va_list_ty
,
va_list_addr
,
reg_top_index
);
let
top
=
in_reg
.load
(
top_type
,
top
,
bx
.tcx
()
.data_layout.pointer_align.abi
);
let
top
=
bx
.struct_gep
(
va_list_ty
,
va_list_addr
,
reg_top_index
);
let
top
=
bx
.load
(
top_type
,
top
,
bx
.tcx
()
.data_layout.pointer_align.abi
);
// reg_value = *(@top + reg_off_v);
let
mut
reg_addr
=
in_reg
.gep
(
bx
.type_i8
(),
top
,
&
[
reg_off_v
]);
let
mut
reg_addr
=
bx
.gep
(
bx
.type_i8
(),
top
,
&
[
reg_off_v
]);
if
bx
.tcx
()
.sess.target.endian
==
Endian
::
Big
&&
layout
.size
.bytes
()
!=
slot_size
{
// On big-endian systems the value is right-aligned in its slot.
let
offset
=
bx
.const_i32
((
slot_size
-
layout
.size
.bytes
())
as
i32
);
reg_addr
=
in_reg
.gep
(
bx
.type_i8
(),
reg_addr
,
&
[
offset
]);
reg_addr
=
bx
.gep
(
bx
.type_i8
(),
reg_addr
,
&
[
offset
]);
}
let
reg_type
=
layout
.llvm_type
(
bx
);
let
reg_addr
=
in_reg
.bitcast
(
reg_addr
,
bx
.cx
.type_ptr_to
(
reg_type
));
let
reg_value
=
in_reg
.load
(
reg_type
,
reg_addr
,
layout
.align.abi
);
in_reg
.br
(
end
);
let
reg_addr
=
bx
.bitcast
(
reg_addr
,
bx
.cx
.type_ptr_to
(
reg_type
));
let
reg_value
=
bx
.load
(
reg_type
,
reg_addr
,
layout
.align.abi
);
bx
.br
(
end
);
// On Stack block
let
mut
on_stack
=
Builder
::
build
(
bx
.cx
,
on_stack
);
bx
.switch_to_block
(
on_stack
);
let
stack_value
=
emit_ptr_va_arg
(
&
mut
on_stack
,
list
,
target_ty
,
false
,
Align
::
from_bytes
(
8
)
.unwrap
(),
true
);
on_stack
.br
(
end
);
emit_ptr_va_arg
(
bx
,
list
,
target_ty
,
false
,
Align
::
from_bytes
(
8
)
.unwrap
(),
true
);
bx
.br
(
end
);
let
mut
end
=
Builder
::
build
(
bx
.cx
,
end
);
let
val
=
end
.phi
(
layout
.immediate_llvm_type
(
bx
),
&
[
reg_value
,
stack_value
],
&
[
in_reg
.llbb
(),
on_stack
.llbb
()],
);
bx
.switch_to_block
(
end
);
let
val
=
bx
.phi
(
layout
.immediate_llvm_type
(
bx
),
&
[
reg_value
,
stack_value
],
&
[
in_reg
,
on_stack
]);
*
bx
=
end
;
val
}
...
...
compiler/rustc_codegen_ssa/src/mir/block.rs
浏览文件 @
96cf7999
...
...
@@ -170,10 +170,9 @@ fn do_call<Bx: BuilderMethods<'a, 'tcx>>(
}
if
let
Some
((
ret_dest
,
target
))
=
destination
{
let
target_llbb
=
fx
.llbb
(
target
);
let
mut
ret_bx
=
Bx
::
build
(
fx
.cx
,
target_llbb
);
fx
.set_debug_loc
(
&
mut
ret_bx
,
self
.terminator.source_info
);
fx
.store_return
(
&
mut
ret_bx
,
ret_dest
,
&
fn_abi
.ret
,
invokeret
);
bx
.switch_to_block
(
fx
.llbb
(
target
));
fx
.set_debug_loc
(
bx
,
self
.terminator.source_info
);
fx
.store_return
(
bx
,
ret_dest
,
&
fn_abi
.ret
,
invokeret
);
}
}
else
{
let
llret
=
bx
.call
(
fn_ty
,
fn_ptr
,
&
llargs
,
self
.funclet
(
fx
));
...
...
@@ -462,7 +461,7 @@ fn codegen_assert_terminator(
}
// After this point, bx is the block for the call to panic.
bx
=
Bx
::
build
(
self
.cx
,
panic_block
);
bx
.switch_to_block
(
panic_block
);
self
.set_debug_loc
(
&
mut
bx
,
terminator
.source_info
);
// Get the location information.
...
...
@@ -914,10 +913,10 @@ fn codegen_call_terminator(
let
bb_fail
=
bx
.append_sibling_block
(
"type_test.fail"
);
bx
.cond_br
(
cond
,
bb_pass
,
bb_fail
);
let
mut
bx_pass
=
Bx
::
build
(
self
.cx
,
bb_pass
);
bx
.switch_to_block
(
bb_pass
);
helper
.do_call
(
self
,
&
mut
bx
_pass
,
&
mut
bx
,
fn_abi
,
fn_ptr
,
&
llargs
,
...
...
@@ -925,9 +924,9 @@ fn codegen_call_terminator(
cleanup
,
);
let
mut
bx_fail
=
Bx
::
build
(
self
.cx
,
bb_fail
);
bx
_fail
.abort
();
bx
_fail
.unreachable
();
bx
.switch_to_block
(
bb_fail
);
bx
.abort
();
bx
.unreachable
();
return
;
}
...
...
compiler/rustc_codegen_ssa/src/traits/builder.rs
浏览文件 @
96cf7999
...
...
@@ -53,6 +53,8 @@ pub trait BuilderMethods<'a, 'tcx>:
fn
append_sibling_block
(
&
mut
self
,
name
:
&
str
)
->
Self
::
BasicBlock
;
fn
switch_to_block
(
&
mut
self
,
llbb
:
Self
::
BasicBlock
);
fn
ret_void
(
&
mut
self
);
fn
ret
(
&
mut
self
,
v
:
Self
::
Value
);
fn
br
(
&
mut
self
,
dest
:
Self
::
BasicBlock
);
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录