forked from M-Labs/artiq
Compare commits
4 Commits
Author | SHA1 | Date |
---|---|---|
whitequark | 2319c31df8 | |
whitequark | b0361111d7 | |
whitequark | a8615cdbcf | |
whitequark | 5da45f9486 |
|
@ -30,9 +30,8 @@ dependencies = [
|
||||||
"build_misoc 0.0.0",
|
"build_misoc 0.0.0",
|
||||||
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"compiler_builtins 0.1.0 (git+https://github.com/rust-lang-nursery/compiler-builtins)",
|
|
||||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"smoltcp 0.4.0 (git+https://github.com/m-labs/smoltcp?rev=181083f)",
|
"smoltcp 0.4.0 (git+https://github.com/m-labs/smoltcp?rev=2139686)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -43,7 +42,7 @@ dependencies = [
|
||||||
"build_misoc 0.0.0",
|
"build_misoc 0.0.0",
|
||||||
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"smoltcp 0.4.0 (git+https://github.com/m-labs/smoltcp?rev=181083f)",
|
"smoltcp 0.4.0 (git+https://github.com/m-labs/smoltcp?rev=2139686)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -70,11 +69,6 @@ name = "cfg-if"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "compiler_builtins"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "git+https://github.com/rust-lang-nursery/compiler-builtins#28daccd9159d33fac5e36394e4f9618db8870dc0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc"
|
name = "crc"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
|
@ -92,6 +86,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
name = "dyld"
|
name = "dyld"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "eh"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"cslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "failure"
|
name = "failure"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -110,7 +111,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fringe"
|
name = "fringe"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
source = "git+https://github.com/m-labs/libfringe?rev=bd23494#bd2349467157969324ca7da5d2ae033c7ffac0c0"
|
source = "git+https://github.com/m-labs/libfringe?rev=b8a6d8f#b8a6d8f68df0edaa3d67d9f3b7b62af9d3bb64a5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
@ -133,6 +134,7 @@ dependencies = [
|
||||||
"build_misoc 0.0.0",
|
"build_misoc 0.0.0",
|
||||||
"cslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"dyld 0.0.0",
|
"dyld 0.0.0",
|
||||||
|
"eh 0.0.0",
|
||||||
"io 0.0.0",
|
"io 0.0.0",
|
||||||
"proto_artiq 0.0.0",
|
"proto_artiq 0.0.0",
|
||||||
]
|
]
|
||||||
|
@ -174,12 +176,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "managed"
|
name = "managed"
|
||||||
version = "0.5.1"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "managed"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -210,15 +207,16 @@ dependencies = [
|
||||||
"build_misoc 0.0.0",
|
"build_misoc 0.0.0",
|
||||||
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"cslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"eh 0.0.0",
|
||||||
"failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"fringe 1.1.0 (git+https://github.com/m-labs/libfringe?rev=bd23494)",
|
"fringe 1.1.0 (git+https://github.com/m-labs/libfringe?rev=b8a6d8f)",
|
||||||
"io 0.0.0",
|
"io 0.0.0",
|
||||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"logger_artiq 0.0.0",
|
"logger_artiq 0.0.0",
|
||||||
"managed 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"managed 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"proto_artiq 0.0.0",
|
"proto_artiq 0.0.0",
|
||||||
"smoltcp 0.4.0 (git+https://github.com/m-labs/smoltcp?rev=181083f)",
|
"smoltcp 0.4.0 (git+https://github.com/m-labs/smoltcp?rev=2139686)",
|
||||||
"unwind_backtrace 0.0.0",
|
"unwind_backtrace 0.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -235,11 +233,12 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smoltcp"
|
name = "smoltcp"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/m-labs/smoltcp?rev=181083f#181083f18c977b8a0463a67e360e4db20594fa21"
|
source = "git+https://github.com/m-labs/smoltcp?rev=2139686#21396867114d267da06f19cc54cc4a1883b900a5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"managed 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"managed 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -284,20 +283,18 @@ version = "0.0.0"
|
||||||
"checksum byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "74c0b906e9446b0a2e4f760cdb3fa4b2c48cdc6db8766a845c54b6ff063fd2e9"
|
"checksum byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "74c0b906e9446b0a2e4f760cdb3fa4b2c48cdc6db8766a845c54b6ff063fd2e9"
|
||||||
"checksum cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0ebb87d1116151416c0cf66a0e3fb6430cccd120fd6300794b4dfaa050ac40ba"
|
"checksum cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0ebb87d1116151416c0cf66a0e3fb6430cccd120fd6300794b4dfaa050ac40ba"
|
||||||
"checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18"
|
"checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18"
|
||||||
"checksum compiler_builtins 0.1.0 (git+https://github.com/rust-lang-nursery/compiler-builtins)" = "<none>"
|
|
||||||
"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb"
|
"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb"
|
||||||
"checksum cslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0f8cb7306107e4b10e64994de6d3274bd08996a7c1322a27b86482392f96be0a"
|
"checksum cslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0f8cb7306107e4b10e64994de6d3274bd08996a7c1322a27b86482392f96be0a"
|
||||||
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
|
"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
|
||||||
"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"
|
"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"
|
||||||
"checksum fringe 1.1.0 (git+https://github.com/m-labs/libfringe?rev=bd23494)" = "<none>"
|
"checksum fringe 1.1.0 (git+https://github.com/m-labs/libfringe?rev=b8a6d8f)" = "<none>"
|
||||||
"checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b"
|
"checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b"
|
||||||
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
|
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
|
||||||
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
|
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
|
||||||
"checksum log_buffer 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f033173c9486b7fe97a79c895c0a3483ae395ab6744c985d10078950e2492419"
|
"checksum log_buffer 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f033173c9486b7fe97a79c895c0a3483ae395ab6744c985d10078950e2492419"
|
||||||
"checksum managed 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "43e2737ecabe4ae36a68061398bf27d2bfd0763f4c3c837a398478459494c4b7"
|
"checksum managed 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba6713e624266d7600e9feae51b1926c6a6a6bebb18ec5a8e11a5f1d5661baba"
|
||||||
"checksum managed 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5a31885241e61ba264d780d2e6686e7e59561c947b4581470364eb3e10102d86"
|
|
||||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||||
"checksum smoltcp 0.4.0 (git+https://github.com/m-labs/smoltcp?rev=181083f)" = "<none>"
|
"checksum smoltcp 0.4.0 (git+https://github.com/m-labs/smoltcp?rev=2139686)" = "<none>"
|
||||||
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
|
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
|
||||||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
||||||
"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd"
|
"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd"
|
||||||
|
|
|
@ -3,4 +3,5 @@ members = ["bootloader", "runtime", "ksupport", "satman"]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
incremental = false # incompatible with LTO
|
incremental = false # incompatible with LTO
|
||||||
|
lto = true
|
||||||
debug = 2
|
debug = 2
|
||||||
|
|
|
@ -19,6 +19,6 @@ board_misoc = { path = "../libboard_misoc", features = ["uart_console", "smoltcp
|
||||||
|
|
||||||
[dependencies.smoltcp]
|
[dependencies.smoltcp]
|
||||||
git = "https://github.com/m-labs/smoltcp"
|
git = "https://github.com/m-labs/smoltcp"
|
||||||
rev = "181083f"
|
rev = "2139686"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["proto-ipv4", "socket-tcp"]
|
features = ["proto-ipv4", "socket-tcp"]
|
||||||
|
|
|
@ -7,8 +7,7 @@ all:: bootloader.bin
|
||||||
|
|
||||||
.PHONY: $(RUSTOUT)/libbootloader.a
|
.PHONY: $(RUSTOUT)/libbootloader.a
|
||||||
$(RUSTOUT)/libbootloader.a:
|
$(RUSTOUT)/libbootloader.a:
|
||||||
$(cargo) --manifest-path $(BOOTLOADER_DIRECTORY)/Cargo.toml -- \
|
$(cargo) --manifest-path $(BOOTLOADER_DIRECTORY)/Cargo.toml
|
||||||
-Clto
|
|
||||||
|
|
||||||
bootloader.elf: $(RUSTOUT)/libbootloader.a
|
bootloader.elf: $(RUSTOUT)/libbootloader.a
|
||||||
$(link) -T $(BOOTLOADER_DIRECTORY)/bootloader.ld
|
$(link) -T $(BOOTLOADER_DIRECTORY)/bootloader.ld
|
||||||
|
|
|
@ -15,6 +15,13 @@ SECTIONS
|
||||||
*(.text .text.*)
|
*(.text .text.*)
|
||||||
} > rom
|
} > rom
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The compiler_builtins crate includes some GOTPC relocations, which require a GOT symbol,
|
||||||
|
* but don't actually need a GOT. This really ought to be fixed on rustc level, but I'm afraid
|
||||||
|
* it will add further complications to our build system that aren't pulling their weight.
|
||||||
|
*/
|
||||||
|
_GLOBAL_OFFSET_TABLE_ = .;
|
||||||
|
|
||||||
.rodata :
|
.rodata :
|
||||||
{
|
{
|
||||||
*(.rodata.*)
|
*(.rodata.*)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![feature(lang_items)]
|
#![feature(panic_implementation, panic_info_message)]
|
||||||
|
|
||||||
extern crate crc;
|
extern crate crc;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
|
@ -188,7 +188,8 @@ fn network_boot() {
|
||||||
println!("Waiting for connections...");
|
println!("Waiting for connections...");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match interface.poll(&mut sockets, clock::get_ms()) {
|
let timestamp = smoltcp::time::Instant::from_millis(clock::get_ms() as i64);
|
||||||
|
match interface.poll(&mut sockets, timestamp) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(smoltcp::Error::Unrecognized) => (),
|
Err(smoltcp::Error::Unrecognized) => (),
|
||||||
Err(err) => println!("Network error: {}", err)
|
Err(err) => println!("Network error: {}", err)
|
||||||
|
@ -232,10 +233,18 @@ pub extern fn abort() {
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle] // https://github.com/rust-lang/rust/issues/{38281,51647}
|
||||||
#[lang = "panic_fmt"]
|
#[panic_implementation]
|
||||||
pub extern fn panic_fmt(args: core::fmt::Arguments, file: &'static str,
|
pub fn panic_fmt(info: &core::panic::PanicInfo) -> ! {
|
||||||
line: u32, column: u32) -> ! {
|
if let Some(location) = info.location() {
|
||||||
println!("panic at {}:{}:{}: {}", file, line, column, args);
|
print!("panic at {}:{}:{}", location.file(), location.line(), location.column());
|
||||||
|
} else {
|
||||||
|
print!("panic at unknown location");
|
||||||
|
}
|
||||||
|
if let Some(message) = info.message() {
|
||||||
|
println!(": {}", message);
|
||||||
|
} else {
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ build_misoc = { path = "../libbuild_misoc" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cslice = { version = "0.3" }
|
cslice = { version = "0.3" }
|
||||||
|
eh = { path = "../libeh" }
|
||||||
io = { path = "../libio", features = ["byteorder"] }
|
io = { path = "../libio", features = ["byteorder"] }
|
||||||
dyld = { path = "../libdyld" }
|
dyld = { path = "../libdyld" }
|
||||||
board_misoc = { path = "../libboard_misoc" }
|
board_misoc = { path = "../libboard_misoc" }
|
||||||
|
|
|
@ -71,9 +71,9 @@ static mut API: &'static [(&'static str, *const ())] = &[
|
||||||
|
|
||||||
/* exceptions */
|
/* exceptions */
|
||||||
api!(_Unwind_Resume = ::unwind::_Unwind_Resume),
|
api!(_Unwind_Resume = ::unwind::_Unwind_Resume),
|
||||||
api!(__artiq_personality = ::eh::personality),
|
api!(__artiq_personality = ::eh_artiq::personality),
|
||||||
api!(__artiq_raise = ::eh::raise),
|
api!(__artiq_raise = ::eh_artiq::raise),
|
||||||
api!(__artiq_reraise = ::eh::reraise),
|
api!(__artiq_reraise = ::eh_artiq::reraise),
|
||||||
|
|
||||||
/* proxified syscalls */
|
/* proxified syscalls */
|
||||||
api!(core_log),
|
api!(core_log),
|
||||||
|
|
|
@ -1,465 +0,0 @@
|
||||||
// Portions of the code in this file are derived from code by:
|
|
||||||
//
|
|
||||||
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
|
||||||
// file 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.
|
|
||||||
#![allow(non_upper_case_globals, non_camel_case_types, dead_code)]
|
|
||||||
|
|
||||||
use core::{ptr, mem};
|
|
||||||
use cslice::CSlice;
|
|
||||||
use unwind as uw;
|
|
||||||
use libc::{c_int, c_void};
|
|
||||||
|
|
||||||
type _Unwind_Stop_Fn = extern "C" fn(version: c_int,
|
|
||||||
actions: uw::_Unwind_Action,
|
|
||||||
exception_class: uw::_Unwind_Exception_Class,
|
|
||||||
exception_object: *mut uw::_Unwind_Exception,
|
|
||||||
context: *mut uw::_Unwind_Context,
|
|
||||||
stop_parameter: *mut c_void)
|
|
||||||
-> uw::_Unwind_Reason_Code;
|
|
||||||
extern {
|
|
||||||
fn _Unwind_ForcedUnwind(exception: *mut uw::_Unwind_Exception,
|
|
||||||
stop_fn: _Unwind_Stop_Fn,
|
|
||||||
stop_parameter: *mut c_void) -> uw::_Unwind_Reason_Code;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DW_EH_PE_omit: u8 = 0xFF;
|
|
||||||
const DW_EH_PE_absptr: u8 = 0x00;
|
|
||||||
|
|
||||||
const DW_EH_PE_uleb128: u8 = 0x01;
|
|
||||||
const DW_EH_PE_udata2: u8 = 0x02;
|
|
||||||
const DW_EH_PE_udata4: u8 = 0x03;
|
|
||||||
const DW_EH_PE_udata8: u8 = 0x04;
|
|
||||||
const DW_EH_PE_sleb128: u8 = 0x09;
|
|
||||||
const DW_EH_PE_sdata2: u8 = 0x0A;
|
|
||||||
const DW_EH_PE_sdata4: u8 = 0x0B;
|
|
||||||
const DW_EH_PE_sdata8: u8 = 0x0C;
|
|
||||||
|
|
||||||
const DW_EH_PE_pcrel: u8 = 0x10;
|
|
||||||
const DW_EH_PE_textrel: u8 = 0x20;
|
|
||||||
const DW_EH_PE_datarel: u8 = 0x30;
|
|
||||||
const DW_EH_PE_funcrel: u8 = 0x40;
|
|
||||||
const DW_EH_PE_aligned: u8 = 0x50;
|
|
||||||
|
|
||||||
const DW_EH_PE_indirect: u8 = 0x80;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct DwarfReader {
|
|
||||||
pub ptr: *const u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DwarfReader {
|
|
||||||
fn new(ptr: *const u8) -> DwarfReader {
|
|
||||||
DwarfReader { ptr: ptr }
|
|
||||||
}
|
|
||||||
|
|
||||||
// DWARF streams are packed, so e.g. a u32 would not necessarily be aligned
|
|
||||||
// on a 4-byte boundary. This may cause problems on platforms with strict
|
|
||||||
// alignment requirements. By wrapping data in a "packed" struct, we are
|
|
||||||
// telling the backend to generate "misalignment-safe" code.
|
|
||||||
unsafe fn read<T: Copy>(&mut self) -> T {
|
|
||||||
let result = ptr::read_unaligned(self.ptr as *const T);
|
|
||||||
self.ptr = self.ptr.offset(mem::size_of::<T>() as isize);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ULEB128 and SLEB128 encodings are defined in Section 7.6 - "Variable
|
|
||||||
// Length Data".
|
|
||||||
unsafe fn read_uleb128(&mut self) -> u64 {
|
|
||||||
let mut shift: usize = 0;
|
|
||||||
let mut result: u64 = 0;
|
|
||||||
let mut byte: u8;
|
|
||||||
loop {
|
|
||||||
byte = self.read::<u8>();
|
|
||||||
result |= ((byte & 0x7F) as u64) << shift;
|
|
||||||
shift += 7;
|
|
||||||
if byte & 0x80 == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn read_sleb128(&mut self) -> i64 {
|
|
||||||
let mut shift: usize = 0;
|
|
||||||
let mut result: u64 = 0;
|
|
||||||
let mut byte: u8;
|
|
||||||
loop {
|
|
||||||
byte = self.read::<u8>();
|
|
||||||
result |= ((byte & 0x7F) as u64) << shift;
|
|
||||||
shift += 7;
|
|
||||||
if byte & 0x80 == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// sign-extend
|
|
||||||
if shift < 8 * mem::size_of::<u64>() && (byte & 0x40) != 0 {
|
|
||||||
result |= (!0 as u64) << shift;
|
|
||||||
}
|
|
||||||
result as i64
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn read_encoded_pointer(&mut self, encoding: u8) -> usize {
|
|
||||||
fn round_up(unrounded: usize, align: usize) -> usize {
|
|
||||||
debug_assert!(align.is_power_of_two());
|
|
||||||
(unrounded + align - 1) & !(align - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_assert!(encoding != DW_EH_PE_omit);
|
|
||||||
|
|
||||||
// DW_EH_PE_aligned implies it's an absolute pointer value
|
|
||||||
if encoding == DW_EH_PE_aligned {
|
|
||||||
self.ptr = round_up(self.ptr as usize, mem::size_of::<usize>()) as *const u8;
|
|
||||||
return self.read::<usize>()
|
|
||||||
}
|
|
||||||
|
|
||||||
let value_ptr = self.ptr;
|
|
||||||
let mut result = match encoding & 0x0F {
|
|
||||||
DW_EH_PE_absptr => self.read::<usize>(),
|
|
||||||
DW_EH_PE_uleb128 => self.read_uleb128() as usize,
|
|
||||||
DW_EH_PE_udata2 => self.read::<u16>() as usize,
|
|
||||||
DW_EH_PE_udata4 => self.read::<u32>() as usize,
|
|
||||||
DW_EH_PE_udata8 => self.read::<u64>() as usize,
|
|
||||||
DW_EH_PE_sleb128 => self.read_sleb128() as usize,
|
|
||||||
DW_EH_PE_sdata2 => self.read::<i16>() as usize,
|
|
||||||
DW_EH_PE_sdata4 => self.read::<i32>() as usize,
|
|
||||||
DW_EH_PE_sdata8 => self.read::<i64>() as usize,
|
|
||||||
_ => panic!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
result += match encoding & 0x70 {
|
|
||||||
DW_EH_PE_absptr => 0,
|
|
||||||
// relative to address of the encoded value, despite the name
|
|
||||||
DW_EH_PE_pcrel => value_ptr as usize,
|
|
||||||
_ => panic!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if encoding & DW_EH_PE_indirect != 0 {
|
|
||||||
result = *(result as *const usize);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn encoding_size(encoding: u8) -> usize {
|
|
||||||
if encoding == DW_EH_PE_omit {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
match encoding & 0x0F {
|
|
||||||
DW_EH_PE_absptr => mem::size_of::<usize>(),
|
|
||||||
DW_EH_PE_udata2 => 2,
|
|
||||||
DW_EH_PE_udata4 => 4,
|
|
||||||
DW_EH_PE_udata8 => 8,
|
|
||||||
DW_EH_PE_sdata2 => 2,
|
|
||||||
DW_EH_PE_sdata4 => 4,
|
|
||||||
DW_EH_PE_sdata8 => 8,
|
|
||||||
_ => panic!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum EHAction {
|
|
||||||
None,
|
|
||||||
Cleanup(usize),
|
|
||||||
Catch(usize),
|
|
||||||
Terminate,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn find_eh_action(lsda: *const u8, func_start: usize, ip: usize,
|
|
||||||
exn_name: CSlice<u8>) -> EHAction {
|
|
||||||
if lsda.is_null() {
|
|
||||||
return EHAction::None
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut reader = DwarfReader::new(lsda);
|
|
||||||
|
|
||||||
let start_encoding = reader.read::<u8>();
|
|
||||||
// base address for landing pad offsets
|
|
||||||
let lpad_base = if start_encoding != DW_EH_PE_omit {
|
|
||||||
reader.read_encoded_pointer(start_encoding)
|
|
||||||
} else {
|
|
||||||
func_start
|
|
||||||
};
|
|
||||||
|
|
||||||
let ttype_encoding = reader.read::<u8>();
|
|
||||||
let ttype_encoding_size = encoding_size(ttype_encoding) as isize;
|
|
||||||
|
|
||||||
let class_info;
|
|
||||||
if ttype_encoding != DW_EH_PE_omit {
|
|
||||||
let class_info_offset = reader.read_uleb128();
|
|
||||||
class_info = reader.ptr.offset(class_info_offset as isize);
|
|
||||||
} else {
|
|
||||||
class_info = ptr::null();
|
|
||||||
}
|
|
||||||
assert!(!class_info.is_null());
|
|
||||||
|
|
||||||
let call_site_encoding = reader.read::<u8>();
|
|
||||||
let call_site_table_length = reader.read_uleb128();
|
|
||||||
let action_table = reader.ptr.offset(call_site_table_length as isize);
|
|
||||||
|
|
||||||
while reader.ptr < action_table {
|
|
||||||
let cs_start = reader.read_encoded_pointer(call_site_encoding);
|
|
||||||
let cs_len = reader.read_encoded_pointer(call_site_encoding);
|
|
||||||
let cs_lpad = reader.read_encoded_pointer(call_site_encoding);
|
|
||||||
let cs_action = reader.read_uleb128();
|
|
||||||
|
|
||||||
if ip < func_start + cs_start {
|
|
||||||
// Callsite table is sorted by cs_start, so if we've passed the ip, we
|
|
||||||
// may stop searching.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if ip > func_start + cs_start + cs_len {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if cs_lpad == 0 {
|
|
||||||
return EHAction::None
|
|
||||||
}
|
|
||||||
|
|
||||||
let lpad = lpad_base + cs_lpad;
|
|
||||||
if cs_action == 0 {
|
|
||||||
return EHAction::Cleanup(lpad)
|
|
||||||
}
|
|
||||||
|
|
||||||
let action_entry = action_table.offset((cs_action - 1) as isize);
|
|
||||||
let mut action_reader = DwarfReader::new(action_entry);
|
|
||||||
loop {
|
|
||||||
let type_info_offset = action_reader.read_sleb128() as isize;
|
|
||||||
let action_offset = action_reader.clone().read_sleb128() as isize;
|
|
||||||
assert!(type_info_offset >= 0);
|
|
||||||
|
|
||||||
if type_info_offset > 0 {
|
|
||||||
let type_info_ptr_ptr = class_info.offset(-type_info_offset * ttype_encoding_size);
|
|
||||||
let type_info_ptr = DwarfReader::new(type_info_ptr_ptr)
|
|
||||||
.read_encoded_pointer(ttype_encoding);
|
|
||||||
let type_info = *(type_info_ptr as *const CSlice<u8>);
|
|
||||||
|
|
||||||
if type_info.as_ref() == exn_name.as_ref() {
|
|
||||||
return EHAction::Catch(lpad)
|
|
||||||
}
|
|
||||||
|
|
||||||
if type_info.len() == 0 {
|
|
||||||
// This is a catch-all clause. We don't compare type_info_ptr with null here
|
|
||||||
// because, in PIC mode, the OR1K LLVM backend emits a literal zero
|
|
||||||
// encoded with DW_EH_PE_pcrel, which of course doesn't result in
|
|
||||||
// a proper null pointer.
|
|
||||||
return EHAction::Catch(lpad)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if action_offset == 0 {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
action_reader.ptr = action_reader.ptr.offset(action_offset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return EHAction::None
|
|
||||||
}
|
|
||||||
|
|
||||||
// the function has a personality but no landing pads; this is fine
|
|
||||||
EHAction::None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct Exception<'a> {
|
|
||||||
pub name: CSlice<'a, u8>,
|
|
||||||
pub file: CSlice<'a, u8>,
|
|
||||||
pub line: u32,
|
|
||||||
pub column: u32,
|
|
||||||
pub function: CSlice<'a, u8>,
|
|
||||||
pub message: CSlice<'a, u8>,
|
|
||||||
pub param: [i64; 3]
|
|
||||||
}
|
|
||||||
|
|
||||||
const EXCEPTION_CLASS: uw::_Unwind_Exception_Class = 0x4d_4c_42_53_41_52_54_51; /* 'MLBSARTQ' */
|
|
||||||
|
|
||||||
const MAX_BACKTRACE_SIZE: usize = 128;
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
struct ExceptionInfo {
|
|
||||||
uw_exception: uw::_Unwind_Exception,
|
|
||||||
exception: Option<Exception<'static>>,
|
|
||||||
handled: bool,
|
|
||||||
backtrace: [usize; MAX_BACKTRACE_SIZE],
|
|
||||||
backtrace_size: usize
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX
|
|
||||||
|
|
||||||
#[cfg(any(target_arch = "or1k"))]
|
|
||||||
const UNWIND_DATA_REG: (i32, i32) = (3, 4); // R3, R4
|
|
||||||
|
|
||||||
#[export_name="__artiq_personality"]
|
|
||||||
pub extern fn personality(version: c_int,
|
|
||||||
actions: uw::_Unwind_Action,
|
|
||||||
uw_exception_class: uw::_Unwind_Exception_Class,
|
|
||||||
uw_exception: *mut uw::_Unwind_Exception,
|
|
||||||
context: *mut uw::_Unwind_Context)
|
|
||||||
-> uw::_Unwind_Reason_Code {
|
|
||||||
unsafe {
|
|
||||||
if version != 1 || uw_exception_class != EXCEPTION_CLASS {
|
|
||||||
return uw::_URC_FATAL_PHASE1_ERROR
|
|
||||||
}
|
|
||||||
|
|
||||||
let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8;
|
|
||||||
let ip = uw::_Unwind_GetIP(context) - 1;
|
|
||||||
let func_start = uw::_Unwind_GetRegionStart(context);
|
|
||||||
|
|
||||||
let exception_info = &mut *(uw_exception as *mut ExceptionInfo);
|
|
||||||
let exception = &exception_info.exception.unwrap();
|
|
||||||
|
|
||||||
let eh_action = find_eh_action(lsda, func_start, ip, exception.name);
|
|
||||||
if actions as u32 & uw::_UA_SEARCH_PHASE as u32 != 0 {
|
|
||||||
match eh_action {
|
|
||||||
EHAction::None |
|
|
||||||
EHAction::Cleanup(_) => return uw::_URC_CONTINUE_UNWIND,
|
|
||||||
EHAction::Catch(_) => return uw::_URC_HANDLER_FOUND,
|
|
||||||
EHAction::Terminate => return uw::_URC_FATAL_PHASE1_ERROR,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match eh_action {
|
|
||||||
EHAction::None => return uw::_URC_CONTINUE_UNWIND,
|
|
||||||
EHAction::Cleanup(lpad) |
|
|
||||||
EHAction::Catch(lpad) => {
|
|
||||||
if actions as u32 & uw::_UA_HANDLER_FRAME as u32 != 0 {
|
|
||||||
exception_info.handled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass a pair of the unwinder exception and ARTIQ exception
|
|
||||||
// (which immediately follows).
|
|
||||||
uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0,
|
|
||||||
uw_exception as uw::_Unwind_Word);
|
|
||||||
uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1,
|
|
||||||
exception as *const _ as uw::_Unwind_Word);
|
|
||||||
uw::_Unwind_SetIP(context, lpad);
|
|
||||||
return uw::_URC_INSTALL_CONTEXT;
|
|
||||||
}
|
|
||||||
EHAction::Terminate => return uw::_URC_FATAL_PHASE2_ERROR,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern fn cleanup(_unwind_code: uw::_Unwind_Reason_Code,
|
|
||||||
uw_exception: *mut uw::_Unwind_Exception) {
|
|
||||||
unsafe {
|
|
||||||
let exception_info = &mut *(uw_exception as *mut ExceptionInfo);
|
|
||||||
|
|
||||||
exception_info.exception = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern fn uncaught_exception(_version: c_int,
|
|
||||||
actions: uw::_Unwind_Action,
|
|
||||||
_uw_exception_class: uw::_Unwind_Exception_Class,
|
|
||||||
uw_exception: *mut uw::_Unwind_Exception,
|
|
||||||
context: *mut uw::_Unwind_Context,
|
|
||||||
_stop_parameter: *mut c_void)
|
|
||||||
-> uw::_Unwind_Reason_Code {
|
|
||||||
unsafe {
|
|
||||||
let exception_info = &mut *(uw_exception as *mut ExceptionInfo);
|
|
||||||
|
|
||||||
if exception_info.backtrace_size < exception_info.backtrace.len() {
|
|
||||||
let ip = uw::_Unwind_GetIP(context);
|
|
||||||
exception_info.backtrace[exception_info.backtrace_size] = ip;
|
|
||||||
exception_info.backtrace_size += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if actions as u32 & uw::_UA_END_OF_STACK as u32 != 0 {
|
|
||||||
::terminate(&exception_info.exception.unwrap(),
|
|
||||||
exception_info.backtrace[..exception_info.backtrace_size].as_mut())
|
|
||||||
} else {
|
|
||||||
uw::_URC_NO_REASON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can unfortunately not use mem::zeroed in a static, so Option<> is used as a workaround.
|
|
||||||
// See https://github.com/rust-lang/rust/issues/39498.
|
|
||||||
static mut INFLIGHT: ExceptionInfo = ExceptionInfo {
|
|
||||||
uw_exception: uw::_Unwind_Exception {
|
|
||||||
exception_class: EXCEPTION_CLASS,
|
|
||||||
exception_cleanup: cleanup,
|
|
||||||
private: [0; uw::unwinder_private_data_size],
|
|
||||||
},
|
|
||||||
exception: None,
|
|
||||||
handled: true,
|
|
||||||
backtrace: [0; MAX_BACKTRACE_SIZE],
|
|
||||||
backtrace_size: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
#[export_name="__artiq_raise"]
|
|
||||||
#[unwind(allowed)]
|
|
||||||
pub unsafe extern fn raise(exception: *const Exception) -> ! {
|
|
||||||
// Zing! The Exception<'a> to Exception<'static> transmute is not really sound in case
|
|
||||||
// the exception is ever captured. Fortunately, they currently aren't, and we save
|
|
||||||
// on the hassle of having to allocate exceptions somewhere except on stack.
|
|
||||||
INFLIGHT.exception = Some(mem::transmute::<Exception, Exception<'static>>(*exception));
|
|
||||||
INFLIGHT.handled = false;
|
|
||||||
|
|
||||||
let result = uw::_Unwind_RaiseException(&mut INFLIGHT.uw_exception);
|
|
||||||
assert!(result == uw::_URC_END_OF_STACK);
|
|
||||||
|
|
||||||
INFLIGHT.backtrace_size = 0;
|
|
||||||
let _result = _Unwind_ForcedUnwind(&mut INFLIGHT.uw_exception,
|
|
||||||
uncaught_exception, ptr::null_mut());
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[export_name="__artiq_reraise"]
|
|
||||||
#[unwind(allowed)]
|
|
||||||
pub unsafe extern fn reraise() -> ! {
|
|
||||||
use cslice::AsCSlice;
|
|
||||||
|
|
||||||
if INFLIGHT.handled {
|
|
||||||
match INFLIGHT.exception {
|
|
||||||
Some(ref exception) => raise(exception),
|
|
||||||
None => raise(&Exception {
|
|
||||||
name: "0:artiq.coredevice.exceptions.RuntimeError".as_c_slice(),
|
|
||||||
file: file!().as_c_slice(),
|
|
||||||
line: line!(),
|
|
||||||
column: column!(),
|
|
||||||
// https://github.com/rust-lang/rfcs/pull/1719
|
|
||||||
function: "__artiq_reraise".as_c_slice(),
|
|
||||||
message: "No active exception to reraise".as_c_slice(),
|
|
||||||
param: [0, 0, 0]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uw::_Unwind_Resume(&mut INFLIGHT.uw_exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stub implementations for the functions the panic_unwind crate expects to be provided.
|
|
||||||
// These all do nothing in libunwind, but aren't built for OR1K.
|
|
||||||
pub mod stubs {
|
|
||||||
#![allow(bad_style, unused_variables)]
|
|
||||||
|
|
||||||
use super::{uw, c_int};
|
|
||||||
|
|
||||||
#[export_name="_Unwind_GetIPInfo"]
|
|
||||||
pub unsafe extern fn _Unwind_GetIPInfo(ctx: *mut uw::_Unwind_Context,
|
|
||||||
ip_before_insn: *mut c_int) -> uw::_Unwind_Word {
|
|
||||||
*ip_before_insn = 0;
|
|
||||||
uw::_Unwind_GetIP(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[export_name="_Unwind_GetTextRelBase"]
|
|
||||||
pub unsafe extern fn _Unwind_GetTextRelBase(ctx: *mut uw::_Unwind_Context) -> uw::_Unwind_Ptr {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[export_name="_Unwind_GetDataRelBase"]
|
|
||||||
pub unsafe extern fn _Unwind_GetDataRelBase(ctx: *mut uw::_Unwind_Context) -> uw::_Unwind_Ptr {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
// Portions of the code in this file are derived from code by:
|
||||||
|
//
|
||||||
|
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file 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.
|
||||||
|
#![allow(private_no_mangle_fns, non_camel_case_types)]
|
||||||
|
|
||||||
|
use core::{ptr, mem};
|
||||||
|
use cslice::CSlice;
|
||||||
|
use unwind as uw;
|
||||||
|
use libc::{c_int, c_void};
|
||||||
|
|
||||||
|
use eh::dwarf::{self, EHAction};
|
||||||
|
|
||||||
|
type _Unwind_Stop_Fn = extern "C" fn(version: c_int,
|
||||||
|
actions: uw::_Unwind_Action,
|
||||||
|
exception_class: uw::_Unwind_Exception_Class,
|
||||||
|
exception_object: *mut uw::_Unwind_Exception,
|
||||||
|
context: *mut uw::_Unwind_Context,
|
||||||
|
stop_parameter: *mut c_void)
|
||||||
|
-> uw::_Unwind_Reason_Code;
|
||||||
|
extern {
|
||||||
|
fn _Unwind_ForcedUnwind(exception: *mut uw::_Unwind_Exception,
|
||||||
|
stop_fn: _Unwind_Stop_Fn,
|
||||||
|
stop_parameter: *mut c_void) -> uw::_Unwind_Reason_Code;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Exception<'a> {
|
||||||
|
pub name: CSlice<'a, u8>,
|
||||||
|
pub file: CSlice<'a, u8>,
|
||||||
|
pub line: u32,
|
||||||
|
pub column: u32,
|
||||||
|
pub function: CSlice<'a, u8>,
|
||||||
|
pub message: CSlice<'a, u8>,
|
||||||
|
pub param: [i64; 3]
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXCEPTION_CLASS: uw::_Unwind_Exception_Class = 0x4d_4c_42_53_41_52_54_51; /* 'MLBSARTQ' */
|
||||||
|
|
||||||
|
const MAX_BACKTRACE_SIZE: usize = 128;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct ExceptionInfo {
|
||||||
|
uw_exception: uw::_Unwind_Exception,
|
||||||
|
exception: Option<Exception<'static>>,
|
||||||
|
handled: bool,
|
||||||
|
backtrace: [usize; MAX_BACKTRACE_SIZE],
|
||||||
|
backtrace_size: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX
|
||||||
|
|
||||||
|
#[cfg(any(target_arch = "or1k"))]
|
||||||
|
const UNWIND_DATA_REG: (i32, i32) = (3, 4); // R3, R4
|
||||||
|
|
||||||
|
#[export_name="__artiq_personality"]
|
||||||
|
pub extern fn personality(version: c_int,
|
||||||
|
actions: uw::_Unwind_Action,
|
||||||
|
uw_exception_class: uw::_Unwind_Exception_Class,
|
||||||
|
uw_exception: *mut uw::_Unwind_Exception,
|
||||||
|
context: *mut uw::_Unwind_Context)
|
||||||
|
-> uw::_Unwind_Reason_Code {
|
||||||
|
unsafe {
|
||||||
|
if version != 1 || uw_exception_class != EXCEPTION_CLASS {
|
||||||
|
return uw::_URC_FATAL_PHASE1_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8;
|
||||||
|
let ip = uw::_Unwind_GetIP(context) - 1;
|
||||||
|
let func_start = uw::_Unwind_GetRegionStart(context);
|
||||||
|
|
||||||
|
let exception_info = &mut *(uw_exception as *mut ExceptionInfo);
|
||||||
|
let exception = &exception_info.exception.unwrap();
|
||||||
|
|
||||||
|
let eh_action = dwarf::find_eh_action(lsda, func_start, ip, exception.name);
|
||||||
|
if actions as u32 & uw::_UA_SEARCH_PHASE as u32 != 0 {
|
||||||
|
match eh_action {
|
||||||
|
EHAction::None |
|
||||||
|
EHAction::Cleanup(_) => return uw::_URC_CONTINUE_UNWIND,
|
||||||
|
EHAction::Catch(_) => return uw::_URC_HANDLER_FOUND,
|
||||||
|
EHAction::Terminate => return uw::_URC_FATAL_PHASE1_ERROR,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match eh_action {
|
||||||
|
EHAction::None => return uw::_URC_CONTINUE_UNWIND,
|
||||||
|
EHAction::Cleanup(lpad) |
|
||||||
|
EHAction::Catch(lpad) => {
|
||||||
|
if actions as u32 & uw::_UA_HANDLER_FRAME as u32 != 0 {
|
||||||
|
exception_info.handled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass a pair of the unwinder exception and ARTIQ exception
|
||||||
|
// (which immediately follows).
|
||||||
|
uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0,
|
||||||
|
uw_exception as uw::_Unwind_Word);
|
||||||
|
uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1,
|
||||||
|
exception as *const _ as uw::_Unwind_Word);
|
||||||
|
uw::_Unwind_SetIP(context, lpad);
|
||||||
|
return uw::_URC_INSTALL_CONTEXT;
|
||||||
|
}
|
||||||
|
EHAction::Terminate => return uw::_URC_FATAL_PHASE2_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern fn cleanup(_unwind_code: uw::_Unwind_Reason_Code,
|
||||||
|
uw_exception: *mut uw::_Unwind_Exception) {
|
||||||
|
unsafe {
|
||||||
|
let exception_info = &mut *(uw_exception as *mut ExceptionInfo);
|
||||||
|
|
||||||
|
exception_info.exception = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern fn uncaught_exception(_version: c_int,
|
||||||
|
actions: uw::_Unwind_Action,
|
||||||
|
_uw_exception_class: uw::_Unwind_Exception_Class,
|
||||||
|
uw_exception: *mut uw::_Unwind_Exception,
|
||||||
|
context: *mut uw::_Unwind_Context,
|
||||||
|
_stop_parameter: *mut c_void)
|
||||||
|
-> uw::_Unwind_Reason_Code {
|
||||||
|
unsafe {
|
||||||
|
let exception_info = &mut *(uw_exception as *mut ExceptionInfo);
|
||||||
|
|
||||||
|
if exception_info.backtrace_size < exception_info.backtrace.len() {
|
||||||
|
let ip = uw::_Unwind_GetIP(context);
|
||||||
|
exception_info.backtrace[exception_info.backtrace_size] = ip;
|
||||||
|
exception_info.backtrace_size += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if actions as u32 & uw::_UA_END_OF_STACK as u32 != 0 {
|
||||||
|
::terminate(&exception_info.exception.unwrap(),
|
||||||
|
exception_info.backtrace[..exception_info.backtrace_size].as_mut())
|
||||||
|
} else {
|
||||||
|
uw::_URC_NO_REASON
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can unfortunately not use mem::zeroed in a static, so Option<> is used as a workaround.
|
||||||
|
// See https://github.com/rust-lang/rust/issues/39498.
|
||||||
|
static mut INFLIGHT: ExceptionInfo = ExceptionInfo {
|
||||||
|
uw_exception: uw::_Unwind_Exception {
|
||||||
|
exception_class: EXCEPTION_CLASS,
|
||||||
|
exception_cleanup: cleanup,
|
||||||
|
private: [0; uw::unwinder_private_data_size],
|
||||||
|
},
|
||||||
|
exception: None,
|
||||||
|
handled: true,
|
||||||
|
backtrace: [0; MAX_BACKTRACE_SIZE],
|
||||||
|
backtrace_size: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
#[export_name="__artiq_raise"]
|
||||||
|
#[unwind(allowed)]
|
||||||
|
pub unsafe extern fn raise(exception: *const Exception) -> ! {
|
||||||
|
// Zing! The Exception<'a> to Exception<'static> transmute is not really sound in case
|
||||||
|
// the exception is ever captured. Fortunately, they currently aren't, and we save
|
||||||
|
// on the hassle of having to allocate exceptions somewhere except on stack.
|
||||||
|
INFLIGHT.exception = Some(mem::transmute::<Exception, Exception<'static>>(*exception));
|
||||||
|
INFLIGHT.handled = false;
|
||||||
|
|
||||||
|
let result = uw::_Unwind_RaiseException(&mut INFLIGHT.uw_exception);
|
||||||
|
assert!(result == uw::_URC_END_OF_STACK);
|
||||||
|
|
||||||
|
INFLIGHT.backtrace_size = 0;
|
||||||
|
let _result = _Unwind_ForcedUnwind(&mut INFLIGHT.uw_exception,
|
||||||
|
uncaught_exception, ptr::null_mut());
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[export_name="__artiq_reraise"]
|
||||||
|
#[unwind(allowed)]
|
||||||
|
pub unsafe extern fn reraise() -> ! {
|
||||||
|
use cslice::AsCSlice;
|
||||||
|
|
||||||
|
if INFLIGHT.handled {
|
||||||
|
match INFLIGHT.exception {
|
||||||
|
Some(ref exception) => raise(exception),
|
||||||
|
None => raise(&Exception {
|
||||||
|
name: "0:artiq.coredevice.exceptions.RuntimeError".as_c_slice(),
|
||||||
|
file: file!().as_c_slice(),
|
||||||
|
line: line!(),
|
||||||
|
column: column!(),
|
||||||
|
// https://github.com/rust-lang/rfcs/pull/1719
|
||||||
|
function: "__artiq_reraise".as_c_slice(),
|
||||||
|
message: "No active exception to reraise".as_c_slice(),
|
||||||
|
param: [0, 0, 0]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uw::_Unwind_Resume(&mut INFLIGHT.uw_exception)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
#![feature(lang_items, asm, libc, panic_unwind, unwind_attributes, global_allocator,
|
#![feature(lang_items, asm, panic_unwind, libc, unwind_attributes,
|
||||||
needs_panic_runtime)]
|
panic_implementation, panic_info_message, nll)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![needs_panic_runtime]
|
|
||||||
|
|
||||||
extern crate cslice;
|
|
||||||
extern crate unwind;
|
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
extern crate unwind;
|
||||||
|
extern crate cslice;
|
||||||
|
|
||||||
|
extern crate eh;
|
||||||
extern crate io;
|
extern crate io;
|
||||||
extern crate dyld;
|
extern crate dyld;
|
||||||
extern crate board_misoc;
|
extern crate board_misoc;
|
||||||
|
@ -46,11 +46,20 @@ macro_rules! recv {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle] // https://github.com/rust-lang/rust/issues/{38281,51647}
|
||||||
#[lang = "panic_fmt"]
|
#[panic_implementation]
|
||||||
pub extern fn panic_fmt(args: core::fmt::Arguments, file: &'static str,
|
pub fn panic_fmt(info: &core::panic::PanicInfo) -> ! {
|
||||||
line: u32, column: u32) -> ! {
|
if let Some(location) = info.location() {
|
||||||
send(&Log(format_args!("panic at {}:{}:{}: {}\n", file, line, column, args)));
|
send(&Log(format_args!("panic at {}:{}:{}",
|
||||||
|
location.file(), location.line(), location.column())));
|
||||||
|
} else {
|
||||||
|
send(&Log(format_args!("panic at unknown location")));
|
||||||
|
}
|
||||||
|
if let Some(message) = info.message() {
|
||||||
|
send(&Log(format_args!("{}\n", message)));
|
||||||
|
} else {
|
||||||
|
send(&Log(format_args!("\n")));
|
||||||
|
}
|
||||||
send(&RunAborted);
|
send(&RunAborted);
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +76,7 @@ macro_rules! println {
|
||||||
macro_rules! raise {
|
macro_rules! raise {
|
||||||
($name:expr, $message:expr, $param0:expr, $param1:expr, $param2:expr) => ({
|
($name:expr, $message:expr, $param0:expr, $param1:expr, $param2:expr) => ({
|
||||||
use cslice::AsCSlice;
|
use cslice::AsCSlice;
|
||||||
let exn = $crate::eh::Exception {
|
let exn = $crate::eh_artiq::Exception {
|
||||||
name: concat!("0:artiq.coredevice.exceptions.", $name).as_c_slice(),
|
name: concat!("0:artiq.coredevice.exceptions.", $name).as_c_slice(),
|
||||||
file: file!().as_c_slice(),
|
file: file!().as_c_slice(),
|
||||||
line: line!(),
|
line: line!(),
|
||||||
|
@ -78,14 +87,14 @@ macro_rules! raise {
|
||||||
param: [$param0, $param1, $param2]
|
param: [$param0, $param1, $param2]
|
||||||
};
|
};
|
||||||
#[allow(unused_unsafe)]
|
#[allow(unused_unsafe)]
|
||||||
unsafe { $crate::eh::raise(&exn) }
|
unsafe { $crate::eh_artiq::raise(&exn) }
|
||||||
});
|
});
|
||||||
($name:expr, $message:expr) => ({
|
($name:expr, $message:expr) => ({
|
||||||
raise!($name, $message, 0, 0, 0)
|
raise!($name, $message, 0, 0, 0)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod eh;
|
mod eh_artiq;
|
||||||
mod api;
|
mod api;
|
||||||
mod rtio;
|
mod rtio;
|
||||||
mod nrt_bus;
|
mod nrt_bus;
|
||||||
|
@ -109,6 +118,7 @@ pub extern fn send_to_rtio_log(timestamp: i64, text: CSlice<u8>) {
|
||||||
rtio::log(timestamp, text.as_ref())
|
rtio::log(timestamp, text.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unwind(aborts)]
|
||||||
extern fn rpc_send(service: u32, tag: CSlice<u8>, data: *const *const ()) {
|
extern fn rpc_send(service: u32, tag: CSlice<u8>, data: *const *const ()) {
|
||||||
while !rpc_queue::empty() {}
|
while !rpc_queue::empty() {}
|
||||||
send(&RpcSend {
|
send(&RpcSend {
|
||||||
|
@ -119,6 +129,7 @@ extern fn rpc_send(service: u32, tag: CSlice<u8>, data: *const *const ()) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unwind(aborts)]
|
||||||
extern fn rpc_send_async(service: u32, tag: CSlice<u8>, data: *const *const ()) {
|
extern fn rpc_send_async(service: u32, tag: CSlice<u8>, data: *const *const ()) {
|
||||||
while rpc_queue::full() {}
|
while rpc_queue::full() {}
|
||||||
rpc_queue::enqueue(|mut slice| {
|
rpc_queue::enqueue(|mut slice| {
|
||||||
|
@ -141,6 +152,7 @@ extern fn rpc_send_async(service: u32, tag: CSlice<u8>, data: *const *const ())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unwind(allowed)]
|
||||||
extern fn rpc_recv(slot: *mut ()) -> usize {
|
extern fn rpc_recv(slot: *mut ()) -> usize {
|
||||||
send(&RpcRecvRequest(slot));
|
send(&RpcRecvRequest(slot));
|
||||||
recv!(&RpcRecvReply(ref result) => {
|
recv!(&RpcRecvReply(ref result) => {
|
||||||
|
@ -148,7 +160,7 @@ extern fn rpc_recv(slot: *mut ()) -> usize {
|
||||||
&Ok(alloc_size) => alloc_size,
|
&Ok(alloc_size) => alloc_size,
|
||||||
&Err(ref exception) =>
|
&Err(ref exception) =>
|
||||||
unsafe {
|
unsafe {
|
||||||
eh::raise(&eh::Exception {
|
eh_artiq::raise(&eh_artiq::Exception {
|
||||||
name: exception.name.as_bytes().as_c_slice(),
|
name: exception.name.as_bytes().as_c_slice(),
|
||||||
file: exception.file.as_bytes().as_c_slice(),
|
file: exception.file.as_bytes().as_c_slice(),
|
||||||
line: exception.line,
|
line: exception.line,
|
||||||
|
@ -162,7 +174,7 @@ extern fn rpc_recv(slot: *mut ()) -> usize {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn terminate(exception: &eh::Exception, backtrace: &mut [usize]) -> ! {
|
fn terminate(exception: &eh_artiq::Exception, backtrace: &mut [usize]) -> ! {
|
||||||
let mut cursor = 0;
|
let mut cursor = 0;
|
||||||
for index in 0..backtrace.len() {
|
for index in 0..backtrace.len() {
|
||||||
if backtrace[index] > kernel_proto::KERNELCPU_PAYLOAD_ADDRESS {
|
if backtrace[index] > kernel_proto::KERNELCPU_PAYLOAD_ADDRESS {
|
||||||
|
@ -188,6 +200,7 @@ fn terminate(exception: &eh::Exception, backtrace: &mut [usize]) -> ! {
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unwind(allowed)]
|
||||||
extern fn watchdog_set(ms: i64) -> i32 {
|
extern fn watchdog_set(ms: i64) -> i32 {
|
||||||
if ms < 0 {
|
if ms < 0 {
|
||||||
raise!("ValueError", "cannot set a watchdog with a negative timeout")
|
raise!("ValueError", "cannot set a watchdog with a negative timeout")
|
||||||
|
@ -197,10 +210,12 @@ extern fn watchdog_set(ms: i64) -> i32 {
|
||||||
recv!(&WatchdogSetReply { id } => id) as i32
|
recv!(&WatchdogSetReply { id } => id) as i32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unwind(aborts)]
|
||||||
extern fn watchdog_clear(id: i32) {
|
extern fn watchdog_clear(id: i32) {
|
||||||
send(&WatchdogClear { id: id as usize })
|
send(&WatchdogClear { id: id as usize })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unwind(aborts)]
|
||||||
extern fn cache_get(key: CSlice<u8>) -> CSlice<'static, i32> {
|
extern fn cache_get(key: CSlice<u8>) -> CSlice<'static, i32> {
|
||||||
send(&CacheGetRequest {
|
send(&CacheGetRequest {
|
||||||
key: str::from_utf8(key.as_ref()).unwrap()
|
key: str::from_utf8(key.as_ref()).unwrap()
|
||||||
|
@ -208,6 +223,7 @@ extern fn cache_get(key: CSlice<u8>) -> CSlice<'static, i32> {
|
||||||
recv!(&CacheGetReply { value } => value.as_c_slice())
|
recv!(&CacheGetReply { value } => value.as_c_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unwind(allowed)]
|
||||||
extern fn cache_put(key: CSlice<u8>, list: CSlice<i32>) {
|
extern fn cache_put(key: CSlice<u8>, list: CSlice<i32>) {
|
||||||
send(&CachePutRequest {
|
send(&CachePutRequest {
|
||||||
key: str::from_utf8(key.as_ref()).unwrap(),
|
key: str::from_utf8(key.as_ref()).unwrap(),
|
||||||
|
@ -241,6 +257,7 @@ fn dma_record_flush() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unwind(allowed)]
|
||||||
extern fn dma_record_start(name: CSlice<u8>) {
|
extern fn dma_record_start(name: CSlice<u8>) {
|
||||||
let name = str::from_utf8(name.as_ref()).unwrap();
|
let name = str::from_utf8(name.as_ref()).unwrap();
|
||||||
|
|
||||||
|
@ -260,6 +277,7 @@ extern fn dma_record_start(name: CSlice<u8>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unwind(allowed)]
|
||||||
extern fn dma_record_stop(duration: i64) {
|
extern fn dma_record_stop(duration: i64) {
|
||||||
unsafe {
|
unsafe {
|
||||||
dma_record_flush();
|
dma_record_flush();
|
||||||
|
@ -281,18 +299,25 @@ extern fn dma_record_stop(duration: i64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern fn dma_record_output(timestamp: i64, channel: i32, address: i32, word: i32) {
|
#[unwind(aborts)]
|
||||||
dma_record_output_wide(timestamp, channel, address, [word].as_c_slice())
|
#[inline(always)]
|
||||||
}
|
unsafe fn dma_record_output_prepare(timestamp: i64, channel: i32, address: i32,
|
||||||
|
words: usize) -> &'static mut [u8] {
|
||||||
extern fn dma_record_output_wide(timestamp: i64, channel: i32, address: i32, words: CSlice<i32>) {
|
|
||||||
assert!(words.len() <= 16); // enforce the hardware limit
|
|
||||||
|
|
||||||
// See gateware/rtio/dma.py.
|
// See gateware/rtio/dma.py.
|
||||||
let header_length = /*length*/1 + /*channel*/3 + /*timestamp*/8 + /*address*/2;
|
const HEADER_LENGTH: usize = /*length*/1 + /*channel*/3 + /*timestamp*/8 + /*address*/2;
|
||||||
let length = header_length + /*data*/words.len() * 4;
|
let length = HEADER_LENGTH + /*data*/words * 4;
|
||||||
|
|
||||||
let header = [
|
if DMA_RECORDER.buffer.len() - DMA_RECORDER.data_len < length {
|
||||||
|
dma_record_flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
let record = &mut DMA_RECORDER.buffer[DMA_RECORDER.data_len..
|
||||||
|
DMA_RECORDER.data_len + length];
|
||||||
|
DMA_RECORDER.data_len += length;
|
||||||
|
|
||||||
|
let (header, data) = record.split_at_mut(HEADER_LENGTH);
|
||||||
|
|
||||||
|
header.copy_from_slice(&[
|
||||||
(length >> 0) as u8,
|
(length >> 0) as u8,
|
||||||
(channel >> 0) as u8,
|
(channel >> 0) as u8,
|
||||||
(channel >> 8) as u8,
|
(channel >> 8) as u8,
|
||||||
|
@ -307,32 +332,43 @@ extern fn dma_record_output_wide(timestamp: i64, channel: i32, address: i32, wor
|
||||||
(timestamp >> 56) as u8,
|
(timestamp >> 56) as u8,
|
||||||
(address >> 0) as u8,
|
(address >> 0) as u8,
|
||||||
(address >> 8) as u8,
|
(address >> 8) as u8,
|
||||||
];
|
]);
|
||||||
|
|
||||||
let mut data = [0; 16 * 4];
|
data
|
||||||
for (i, &word) in words.as_ref().iter().enumerate() {
|
}
|
||||||
let part = [
|
|
||||||
|
#[unwind(aborts)]
|
||||||
|
extern fn dma_record_output(timestamp: i64, channel: i32, address: i32, word: i32) {
|
||||||
|
unsafe {
|
||||||
|
let data = dma_record_output_prepare(timestamp, channel, address, 1);
|
||||||
|
data.copy_from_slice(&[
|
||||||
(word >> 0) as u8,
|
(word >> 0) as u8,
|
||||||
(word >> 8) as u8,
|
(word >> 8) as u8,
|
||||||
(word >> 16) as u8,
|
(word >> 16) as u8,
|
||||||
(word >> 24) as u8,
|
(word >> 24) as u8,
|
||||||
];
|
]);
|
||||||
data[i * 4..(i + 1) * 4].copy_from_slice(&part[..]);
|
|
||||||
}
|
|
||||||
let data = &data[..words.len() * 4];
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
if DMA_RECORDER.buffer.len() - DMA_RECORDER.data_len < length {
|
|
||||||
dma_record_flush()
|
|
||||||
}
|
|
||||||
let dst = &mut DMA_RECORDER.buffer[DMA_RECORDER.data_len..
|
|
||||||
DMA_RECORDER.data_len + length];
|
|
||||||
dst[..header_length].copy_from_slice(&header[..]);
|
|
||||||
dst[header_length..].copy_from_slice(&data[..]);
|
|
||||||
DMA_RECORDER.data_len += length;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unwind(aborts)]
|
||||||
|
extern fn dma_record_output_wide(timestamp: i64, channel: i32, address: i32, words: CSlice<i32>) {
|
||||||
|
assert!(words.len() <= 16); // enforce the hardware limit
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut data = dma_record_output_prepare(timestamp, channel, address, 1);
|
||||||
|
for word in words.as_ref().iter() {
|
||||||
|
data[..4].copy_from_slice(&[
|
||||||
|
(word >> 0) as u8,
|
||||||
|
(word >> 8) as u8,
|
||||||
|
(word >> 16) as u8,
|
||||||
|
(word >> 24) as u8,
|
||||||
|
]);
|
||||||
|
data = &mut data[4..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unwind(aborts)]
|
||||||
extern fn dma_erase(name: CSlice<u8>) {
|
extern fn dma_erase(name: CSlice<u8>) {
|
||||||
let name = str::from_utf8(name.as_ref()).unwrap();
|
let name = str::from_utf8(name.as_ref()).unwrap();
|
||||||
|
|
||||||
|
@ -345,6 +381,7 @@ struct DmaTrace {
|
||||||
address: i32,
|
address: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[unwind(allowed)]
|
||||||
extern fn dma_retrieve(name: CSlice<u8>) -> DmaTrace {
|
extern fn dma_retrieve(name: CSlice<u8>) -> DmaTrace {
|
||||||
let name = str::from_utf8(name.as_ref()).unwrap();
|
let name = str::from_utf8(name.as_ref()).unwrap();
|
||||||
|
|
||||||
|
@ -365,6 +402,7 @@ extern fn dma_retrieve(name: CSlice<u8>) -> DmaTrace {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(has_rtio_dma)]
|
#[cfg(has_rtio_dma)]
|
||||||
|
#[unwind(allowed)]
|
||||||
extern fn dma_playback(timestamp: i64, ptr: i32) {
|
extern fn dma_playback(timestamp: i64, ptr: i32) {
|
||||||
assert!(ptr % 64 == 0);
|
assert!(ptr % 64 == 0);
|
||||||
|
|
||||||
|
@ -399,6 +437,7 @@ extern fn dma_playback(timestamp: i64, ptr: i32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(has_rtio_dma))]
|
#[cfg(not(has_rtio_dma))]
|
||||||
|
#[unwind(allowed)]
|
||||||
extern fn dma_playback(_timestamp: i64, _ptr: i32) {
|
extern fn dma_playback(_timestamp: i64, _ptr: i32) {
|
||||||
unimplemented!("not(has_rtio_dma)")
|
unimplemented!("not(has_rtio_dma)")
|
||||||
}
|
}
|
||||||
|
@ -485,11 +524,13 @@ pub unsafe fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
#[unwind(allowed)]
|
||||||
pub extern fn exception(vect: u32, _regs: *const u32, pc: u32, ea: u32) {
|
pub extern fn exception(vect: u32, _regs: *const u32, pc: u32, ea: u32) {
|
||||||
panic!("exception {:?} at PC 0x{:x}, EA 0x{:x}", vect, pc, ea)
|
panic!("exception {:?} at PC 0x{:x}, EA 0x{:x}", vect, pc, ea)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
#[unwind(allowed)]
|
||||||
pub extern fn abort() {
|
pub extern fn abort() {
|
||||||
panic!("aborted")
|
panic!("aborted")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
#![feature(alloc, allocator_api)]
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
extern crate alloc;
|
use core::{ptr, mem, fmt};
|
||||||
|
use core::alloc::{GlobalAlloc, Layout};
|
||||||
use core::{mem, fmt};
|
|
||||||
use alloc::allocator::{Layout, AllocErr, Alloc};
|
|
||||||
|
|
||||||
// The minimum alignment guaranteed by the architecture.
|
// The minimum alignment guaranteed by the architecture.
|
||||||
const MIN_ALIGN: usize = 4;
|
const MIN_ALIGN: usize = 4;
|
||||||
|
@ -42,10 +39,10 @@ impl ListAlloc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<'a> Alloc for &'a ListAlloc {
|
unsafe impl GlobalAlloc for ListAlloc {
|
||||||
unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, AllocErr> {
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||||
if layout.align() > MIN_ALIGN {
|
if layout.align() > MIN_ALIGN {
|
||||||
return Err(AllocErr::Unsupported { details: "alignment too large" })
|
panic!("cannot allocate with alignment {}", layout.align())
|
||||||
}
|
}
|
||||||
|
|
||||||
let header_size = mem::size_of::<Header>();
|
let header_size = mem::size_of::<Header>();
|
||||||
|
@ -83,7 +80,7 @@ unsafe impl<'a> Alloc for &'a ListAlloc {
|
||||||
|
|
||||||
if (*curr).size >= size {
|
if (*curr).size >= size {
|
||||||
(*curr).magic = MAGIC_BUSY;
|
(*curr).magic = MAGIC_BUSY;
|
||||||
return Ok(curr.offset(1) as *mut u8)
|
return curr.offset(1) as *mut u8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => panic!("heap corruption detected at {:p}", curr)
|
_ => panic!("heap corruption detected at {:p}", curr)
|
||||||
|
@ -92,20 +89,16 @@ unsafe impl<'a> Alloc for &'a ListAlloc {
|
||||||
curr = (*curr).next;
|
curr = (*curr).next;
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(AllocErr::Exhausted { request: layout })
|
ptr::null_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn dealloc(&mut self, ptr: *mut u8, _layout: Layout) {
|
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
|
||||||
let curr = (ptr as *mut Header).offset(-1);
|
let curr = (ptr as *mut Header).offset(-1);
|
||||||
if (*curr).magic != MAGIC_BUSY {
|
if (*curr).magic != MAGIC_BUSY {
|
||||||
panic!("heap corruption detected at {:p}", curr)
|
panic!("heap corruption detected at {:p}", curr)
|
||||||
}
|
}
|
||||||
(*curr).magic = MAGIC_FREE;
|
(*curr).magic = MAGIC_FREE;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn oom(&mut self, err: AllocErr) -> ! {
|
|
||||||
panic!("heap view: {}\ncannot allocate: {:?}", self, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ListAlloc {
|
impl fmt::Display for ListAlloc {
|
||||||
|
|
|
@ -13,13 +13,12 @@ cc = "1.0"
|
||||||
build_misoc = { path = "../libbuild_misoc" }
|
build_misoc = { path = "../libbuild_misoc" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
compiler_builtins = { git = "https://github.com/rust-lang-nursery/compiler-builtins", features = ["mem"] }
|
|
||||||
byteorder = { version = "1.0", default-features = false }
|
byteorder = { version = "1.0", default-features = false }
|
||||||
log = { version = "0.4", default-features = false, optional = true }
|
log = { version = "0.4", default-features = false, optional = true }
|
||||||
|
|
||||||
[dependencies.smoltcp]
|
[dependencies.smoltcp]
|
||||||
git = "https://github.com/m-labs/smoltcp"
|
git = "https://github.com/m-labs/smoltcp"
|
||||||
rev = "181083f"
|
rev = "2139686" # NB: also change in runtime/Cargo.toml
|
||||||
default-features = false
|
default-features = false
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use core::{slice, fmt};
|
use core::{slice, fmt};
|
||||||
use smoltcp::Result;
|
use smoltcp::Result;
|
||||||
|
use smoltcp::time::Instant;
|
||||||
use smoltcp::phy::{self, DeviceCapabilities, Device};
|
use smoltcp::phy::{self, DeviceCapabilities, Device};
|
||||||
|
|
||||||
use csr;
|
use csr;
|
||||||
|
@ -95,7 +96,7 @@ impl<'a> Device<'a> for EthernetDevice {
|
||||||
pub struct EthernetRxSlot(usize);
|
pub struct EthernetRxSlot(usize);
|
||||||
|
|
||||||
impl phy::RxToken for EthernetRxSlot {
|
impl phy::RxToken for EthernetRxSlot {
|
||||||
fn consume<R, F>(self, _timestamp: u64, f: F) -> Result<R>
|
fn consume<R, F>(self, _timestamp: Instant, f: F) -> Result<R>
|
||||||
where F: FnOnce(&[u8]) -> Result<R>
|
where F: FnOnce(&[u8]) -> Result<R>
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -110,7 +111,7 @@ impl phy::RxToken for EthernetRxSlot {
|
||||||
pub struct EthernetTxSlot(usize);
|
pub struct EthernetTxSlot(usize);
|
||||||
|
|
||||||
impl phy::TxToken for EthernetTxSlot {
|
impl phy::TxToken for EthernetTxSlot {
|
||||||
fn consume<R, F>(self, _timestamp: u64, length: usize, f: F) -> Result<R>
|
fn consume<R, F>(self, _timestamp: Instant, length: usize, f: F) -> Result<R>
|
||||||
where F: FnOnce(&mut [u8]) -> Result<R>
|
where F: FnOnce(&mut [u8]) -> Result<R>
|
||||||
{
|
{
|
||||||
debug_assert!(length < SLOT_SIZE);
|
debug_assert!(length < SLOT_SIZE);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![feature(compiler_builtins_lib, asm, try_from)]
|
#![feature(asm, try_from)]
|
||||||
|
|
||||||
extern crate compiler_builtins;
|
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
#[cfg(feature = "log")]
|
#[cfg(feature = "log")]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
authors = ["M-Labs"]
|
||||||
|
name = "eh"
|
||||||
|
version = "0.0.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "eh"
|
||||||
|
path = "lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cslice = { version = "0.3" }
|
|
@ -0,0 +1,243 @@
|
||||||
|
#![allow(non_upper_case_globals, dead_code)]
|
||||||
|
|
||||||
|
use core::{ptr, mem};
|
||||||
|
use cslice::CSlice;
|
||||||
|
|
||||||
|
const DW_EH_PE_omit: u8 = 0xFF;
|
||||||
|
const DW_EH_PE_absptr: u8 = 0x00;
|
||||||
|
|
||||||
|
const DW_EH_PE_uleb128: u8 = 0x01;
|
||||||
|
const DW_EH_PE_udata2: u8 = 0x02;
|
||||||
|
const DW_EH_PE_udata4: u8 = 0x03;
|
||||||
|
const DW_EH_PE_udata8: u8 = 0x04;
|
||||||
|
const DW_EH_PE_sleb128: u8 = 0x09;
|
||||||
|
const DW_EH_PE_sdata2: u8 = 0x0A;
|
||||||
|
const DW_EH_PE_sdata4: u8 = 0x0B;
|
||||||
|
const DW_EH_PE_sdata8: u8 = 0x0C;
|
||||||
|
|
||||||
|
const DW_EH_PE_pcrel: u8 = 0x10;
|
||||||
|
const DW_EH_PE_textrel: u8 = 0x20;
|
||||||
|
const DW_EH_PE_datarel: u8 = 0x30;
|
||||||
|
const DW_EH_PE_funcrel: u8 = 0x40;
|
||||||
|
const DW_EH_PE_aligned: u8 = 0x50;
|
||||||
|
|
||||||
|
const DW_EH_PE_indirect: u8 = 0x80;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct DwarfReader {
|
||||||
|
pub ptr: *const u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DwarfReader {
|
||||||
|
fn new(ptr: *const u8) -> DwarfReader {
|
||||||
|
DwarfReader { ptr: ptr }
|
||||||
|
}
|
||||||
|
|
||||||
|
// DWARF streams are packed, so e.g. a u32 would not necessarily be aligned
|
||||||
|
// on a 4-byte boundary. This may cause problems on platforms with strict
|
||||||
|
// alignment requirements. By wrapping data in a "packed" struct, we are
|
||||||
|
// telling the backend to generate "misalignment-safe" code.
|
||||||
|
unsafe fn read<T: Copy>(&mut self) -> T {
|
||||||
|
let result = ptr::read_unaligned(self.ptr as *const T);
|
||||||
|
self.ptr = self.ptr.offset(mem::size_of::<T>() as isize);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ULEB128 and SLEB128 encodings are defined in Section 7.6 - "Variable
|
||||||
|
// Length Data".
|
||||||
|
unsafe fn read_uleb128(&mut self) -> u64 {
|
||||||
|
let mut shift: usize = 0;
|
||||||
|
let mut result: u64 = 0;
|
||||||
|
let mut byte: u8;
|
||||||
|
loop {
|
||||||
|
byte = self.read::<u8>();
|
||||||
|
result |= ((byte & 0x7F) as u64) << shift;
|
||||||
|
shift += 7;
|
||||||
|
if byte & 0x80 == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn read_sleb128(&mut self) -> i64 {
|
||||||
|
let mut shift: usize = 0;
|
||||||
|
let mut result: u64 = 0;
|
||||||
|
let mut byte: u8;
|
||||||
|
loop {
|
||||||
|
byte = self.read::<u8>();
|
||||||
|
result |= ((byte & 0x7F) as u64) << shift;
|
||||||
|
shift += 7;
|
||||||
|
if byte & 0x80 == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// sign-extend
|
||||||
|
if shift < 8 * mem::size_of::<u64>() && (byte & 0x40) != 0 {
|
||||||
|
result |= (!0 as u64) << shift;
|
||||||
|
}
|
||||||
|
result as i64
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn read_encoded_pointer(&mut self, encoding: u8) -> usize {
|
||||||
|
fn round_up(unrounded: usize, align: usize) -> usize {
|
||||||
|
debug_assert!(align.is_power_of_two());
|
||||||
|
(unrounded + align - 1) & !(align - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_assert!(encoding != DW_EH_PE_omit);
|
||||||
|
|
||||||
|
// DW_EH_PE_aligned implies it's an absolute pointer value
|
||||||
|
if encoding == DW_EH_PE_aligned {
|
||||||
|
self.ptr = round_up(self.ptr as usize, mem::size_of::<usize>()) as *const u8;
|
||||||
|
return self.read::<usize>()
|
||||||
|
}
|
||||||
|
|
||||||
|
let value_ptr = self.ptr;
|
||||||
|
let mut result = match encoding & 0x0F {
|
||||||
|
DW_EH_PE_absptr => self.read::<usize>(),
|
||||||
|
DW_EH_PE_uleb128 => self.read_uleb128() as usize,
|
||||||
|
DW_EH_PE_udata2 => self.read::<u16>() as usize,
|
||||||
|
DW_EH_PE_udata4 => self.read::<u32>() as usize,
|
||||||
|
DW_EH_PE_udata8 => self.read::<u64>() as usize,
|
||||||
|
DW_EH_PE_sleb128 => self.read_sleb128() as usize,
|
||||||
|
DW_EH_PE_sdata2 => self.read::<i16>() as usize,
|
||||||
|
DW_EH_PE_sdata4 => self.read::<i32>() as usize,
|
||||||
|
DW_EH_PE_sdata8 => self.read::<i64>() as usize,
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
result += match encoding & 0x70 {
|
||||||
|
DW_EH_PE_absptr => 0,
|
||||||
|
// relative to address of the encoded value, despite the name
|
||||||
|
DW_EH_PE_pcrel => value_ptr as usize,
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if encoding & DW_EH_PE_indirect != 0 {
|
||||||
|
result = *(result as *const usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encoding_size(encoding: u8) -> usize {
|
||||||
|
if encoding == DW_EH_PE_omit {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
match encoding & 0x0F {
|
||||||
|
DW_EH_PE_absptr => mem::size_of::<usize>(),
|
||||||
|
DW_EH_PE_udata2 => 2,
|
||||||
|
DW_EH_PE_udata4 => 4,
|
||||||
|
DW_EH_PE_udata8 => 8,
|
||||||
|
DW_EH_PE_sdata2 => 2,
|
||||||
|
DW_EH_PE_sdata4 => 4,
|
||||||
|
DW_EH_PE_sdata8 => 8,
|
||||||
|
_ => panic!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum EHAction {
|
||||||
|
None,
|
||||||
|
Cleanup(usize),
|
||||||
|
Catch(usize),
|
||||||
|
Terminate,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn find_eh_action(lsda: *const u8, func_start: usize, ip: usize,
|
||||||
|
exn_name: CSlice<u8>) -> EHAction {
|
||||||
|
if lsda.is_null() {
|
||||||
|
return EHAction::None
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut reader = DwarfReader::new(lsda);
|
||||||
|
|
||||||
|
let start_encoding = reader.read::<u8>();
|
||||||
|
// base address for landing pad offsets
|
||||||
|
let lpad_base = if start_encoding != DW_EH_PE_omit {
|
||||||
|
reader.read_encoded_pointer(start_encoding)
|
||||||
|
} else {
|
||||||
|
func_start
|
||||||
|
};
|
||||||
|
|
||||||
|
let ttype_encoding = reader.read::<u8>();
|
||||||
|
let ttype_encoding_size = encoding_size(ttype_encoding) as isize;
|
||||||
|
|
||||||
|
let class_info;
|
||||||
|
if ttype_encoding != DW_EH_PE_omit {
|
||||||
|
let class_info_offset = reader.read_uleb128();
|
||||||
|
class_info = reader.ptr.offset(class_info_offset as isize);
|
||||||
|
} else {
|
||||||
|
class_info = ptr::null();
|
||||||
|
}
|
||||||
|
assert!(!class_info.is_null());
|
||||||
|
|
||||||
|
let call_site_encoding = reader.read::<u8>();
|
||||||
|
let call_site_table_length = reader.read_uleb128();
|
||||||
|
let action_table = reader.ptr.offset(call_site_table_length as isize);
|
||||||
|
|
||||||
|
while reader.ptr < action_table {
|
||||||
|
let cs_start = reader.read_encoded_pointer(call_site_encoding);
|
||||||
|
let cs_len = reader.read_encoded_pointer(call_site_encoding);
|
||||||
|
let cs_lpad = reader.read_encoded_pointer(call_site_encoding);
|
||||||
|
let cs_action = reader.read_uleb128();
|
||||||
|
|
||||||
|
if ip < func_start + cs_start {
|
||||||
|
// Callsite table is sorted by cs_start, so if we've passed the ip, we
|
||||||
|
// may stop searching.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ip > func_start + cs_start + cs_len {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if cs_lpad == 0 {
|
||||||
|
return EHAction::None
|
||||||
|
}
|
||||||
|
|
||||||
|
let lpad = lpad_base + cs_lpad;
|
||||||
|
if cs_action == 0 {
|
||||||
|
return EHAction::Cleanup(lpad)
|
||||||
|
}
|
||||||
|
|
||||||
|
let action_entry = action_table.offset((cs_action - 1) as isize);
|
||||||
|
let mut action_reader = DwarfReader::new(action_entry);
|
||||||
|
loop {
|
||||||
|
let type_info_offset = action_reader.read_sleb128() as isize;
|
||||||
|
let action_offset = action_reader.clone().read_sleb128() as isize;
|
||||||
|
assert!(type_info_offset >= 0);
|
||||||
|
|
||||||
|
if type_info_offset > 0 {
|
||||||
|
let type_info_ptr_ptr = class_info.offset(-type_info_offset * ttype_encoding_size);
|
||||||
|
let type_info_ptr = DwarfReader::new(type_info_ptr_ptr)
|
||||||
|
.read_encoded_pointer(ttype_encoding);
|
||||||
|
let type_info = *(type_info_ptr as *const CSlice<u8>);
|
||||||
|
|
||||||
|
if type_info.as_ref() == exn_name.as_ref() {
|
||||||
|
return EHAction::Catch(lpad)
|
||||||
|
}
|
||||||
|
|
||||||
|
if type_info.len() == 0 {
|
||||||
|
// This is a catch-all clause. We don't compare type_info_ptr with null here
|
||||||
|
// because, in PIC mode, the OR1K LLVM backend emits a literal zero
|
||||||
|
// encoded with DW_EH_PE_pcrel, which of course doesn't result in
|
||||||
|
// a proper null pointer.
|
||||||
|
return EHAction::Catch(lpad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if action_offset == 0 {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
action_reader.ptr = action_reader.ptr.offset(action_offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return EHAction::None
|
||||||
|
}
|
||||||
|
|
||||||
|
// the function has a personality but no landing pads; this is fine
|
||||||
|
EHAction::None
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright 2015 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.
|
||||||
|
|
||||||
|
// This is the Rust personality function, adapted for use in ARTIQ. We never actually panic
|
||||||
|
// from Rust or recover from Rust exceptions (there's nothing to catch the panics), but we
|
||||||
|
// need a personality function to step back through Rust frames in order to make a backtrace.
|
||||||
|
//
|
||||||
|
// By design, this personality function is only ever called in the search phase, although
|
||||||
|
// to keep things simple and close to upstream, it is not modified
|
||||||
|
#![allow(private_no_mangle_fns)]
|
||||||
|
|
||||||
|
use unwind as uw;
|
||||||
|
use libc::{c_int, uintptr_t};
|
||||||
|
use cslice::AsCSlice;
|
||||||
|
|
||||||
|
use dwarf::{self, EHAction};
|
||||||
|
|
||||||
|
// Register ids were lifted from LLVM's TargetLowering::getExceptionPointerRegister()
|
||||||
|
// and TargetLowering::getExceptionSelectorRegister() for each architecture,
|
||||||
|
// then mapped to DWARF register numbers via register definition tables
|
||||||
|
// (typically <arch>RegisterInfo.td, search for "DwarfRegNum").
|
||||||
|
// See also http://llvm.org/docs/WritingAnLLVMBackend.html#defining-a-register.
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86")]
|
||||||
|
const UNWIND_DATA_REG: (i32, i32) = (0, 2); // EAX, EDX
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX
|
||||||
|
|
||||||
|
#[cfg(any(target_arch = "or1k"))]
|
||||||
|
const UNWIND_DATA_REG: (i32, i32) = (3, 4); // R3, R4
|
||||||
|
|
||||||
|
// The following code is based on GCC's C and C++ personality routines. For reference, see:
|
||||||
|
// https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/eh_personality.cc
|
||||||
|
// https://github.com/gcc-mirror/gcc/blob/trunk/libgcc/unwind-c.c
|
||||||
|
#[lang = "eh_personality"]
|
||||||
|
#[no_mangle]
|
||||||
|
#[allow(unused)]
|
||||||
|
unsafe extern "C" fn rust_eh_personality(version: c_int,
|
||||||
|
actions: uw::_Unwind_Action,
|
||||||
|
exception_class: uw::_Unwind_Exception_Class,
|
||||||
|
exception_object: *mut uw::_Unwind_Exception,
|
||||||
|
context: *mut uw::_Unwind_Context)
|
||||||
|
-> uw::_Unwind_Reason_Code {
|
||||||
|
if version != 1 {
|
||||||
|
return uw::_URC_FATAL_PHASE1_ERROR;
|
||||||
|
}
|
||||||
|
let eh_action = match find_eh_action(context) {
|
||||||
|
Ok(action) => action,
|
||||||
|
Err(_) => return uw::_URC_FATAL_PHASE1_ERROR,
|
||||||
|
};
|
||||||
|
if actions as i32 & uw::_UA_SEARCH_PHASE as i32 != 0 {
|
||||||
|
match eh_action {
|
||||||
|
EHAction::None |
|
||||||
|
EHAction::Cleanup(_) => return uw::_URC_CONTINUE_UNWIND,
|
||||||
|
EHAction::Catch(_) => return uw::_URC_HANDLER_FOUND,
|
||||||
|
EHAction::Terminate => return uw::_URC_FATAL_PHASE1_ERROR,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match eh_action {
|
||||||
|
EHAction::None => return uw::_URC_CONTINUE_UNWIND,
|
||||||
|
EHAction::Cleanup(lpad) |
|
||||||
|
EHAction::Catch(lpad) => {
|
||||||
|
uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, exception_object as uintptr_t);
|
||||||
|
uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, 0);
|
||||||
|
uw::_Unwind_SetIP(context, lpad);
|
||||||
|
return uw::_URC_INSTALL_CONTEXT;
|
||||||
|
}
|
||||||
|
EHAction::Terminate => return uw::_URC_FATAL_PHASE2_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn find_eh_action(context: *mut uw::_Unwind_Context)
|
||||||
|
-> Result<EHAction, ()>
|
||||||
|
{
|
||||||
|
let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8;
|
||||||
|
let func = uw::_Unwind_GetRegionStart(context);
|
||||||
|
let ip = uw::_Unwind_GetIP(context);
|
||||||
|
Ok(dwarf::find_eh_action(lsda, func, ip, [].as_c_slice()))
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
#![feature(lang_items, panic_unwind, libc, unwind_attributes)]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
extern crate cslice;
|
||||||
|
extern crate unwind;
|
||||||
|
extern crate libc;
|
||||||
|
|
||||||
|
pub mod dwarf;
|
||||||
|
pub mod eh_rust;
|
|
@ -18,7 +18,8 @@ failure_derive = { version = "0.1", default-features = false }
|
||||||
byteorder = { version = "1.0", default-features = false }
|
byteorder = { version = "1.0", default-features = false }
|
||||||
cslice = { version = "0.3" }
|
cslice = { version = "0.3" }
|
||||||
log = { version = "0.4", default-features = false }
|
log = { version = "0.4", default-features = false }
|
||||||
managed = { version = "0.6", default-features = false, features = ["alloc", "map"] }
|
managed = { version = "= 0.7.0", default-features = false, features = ["alloc", "map"] }
|
||||||
|
eh = { path = "../libeh" }
|
||||||
unwind_backtrace = { path = "../libunwind_backtrace" }
|
unwind_backtrace = { path = "../libunwind_backtrace" }
|
||||||
io = { path = "../libio", features = ["byteorder"] }
|
io = { path = "../libio", features = ["byteorder"] }
|
||||||
alloc_list = { path = "../liballoc_list" }
|
alloc_list = { path = "../liballoc_list" }
|
||||||
|
@ -29,12 +30,12 @@ proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] }
|
||||||
|
|
||||||
[dependencies.fringe]
|
[dependencies.fringe]
|
||||||
git = "https://github.com/m-labs/libfringe"
|
git = "https://github.com/m-labs/libfringe"
|
||||||
rev = "bd23494"
|
rev = "b8a6d8f"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["alloc"]
|
features = ["alloc"]
|
||||||
|
|
||||||
[dependencies.smoltcp]
|
[dependencies.smoltcp]
|
||||||
git = "https://github.com/m-labs/smoltcp"
|
git = "https://github.com/m-labs/smoltcp"
|
||||||
rev = "181083f"
|
rev = "2139686" # NB: also change in libboard_misoc/Cargo.toml
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["alloc", "log", "proto-ipv4", "socket-tcp"]
|
features = ["rust-1.28", "alloc", "log", "proto-ipv4", "socket-tcp"]
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
#![feature(lang_items, alloc, global_allocator, try_from, nonzero, nll, needs_panic_runtime, asm)]
|
#![feature(lang_items, alloc, try_from, nonzero, asm,
|
||||||
|
panic_implementation, panic_info_message)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![needs_panic_runtime]
|
|
||||||
|
|
||||||
|
extern crate eh;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
extern crate failure;
|
extern crate failure;
|
||||||
|
@ -250,18 +251,18 @@ fn startup_ethernet() {
|
||||||
net_device.reset_phy_if_any();
|
net_device.reset_phy_if_any();
|
||||||
|
|
||||||
let net_device = {
|
let net_device = {
|
||||||
|
use smoltcp::time::Instant;
|
||||||
use smoltcp::wire::PrettyPrinter;
|
use smoltcp::wire::PrettyPrinter;
|
||||||
use smoltcp::wire::EthernetFrame;
|
use smoltcp::wire::EthernetFrame;
|
||||||
|
|
||||||
fn net_trace_writer(timestamp: u64, printer: PrettyPrinter<EthernetFrame<&[u8]>>) {
|
fn net_trace_writer(timestamp: Instant, printer: PrettyPrinter<EthernetFrame<&[u8]>>) {
|
||||||
let seconds = timestamp / 1000;
|
print!("\x1b[37m[{:6}.{:03}s]\n{}\x1b[0m\n",
|
||||||
let micros = timestamp % 1000 * 1000;
|
timestamp.secs(), timestamp.millis(), printer)
|
||||||
print!("\x1b[37m[{:6}.{:06}s]\n{}\x1b[0m\n", seconds, micros, printer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn net_trace_silent(_timestamp: u64, _printer: PrettyPrinter<EthernetFrame<&[u8]>>) {}
|
fn net_trace_silent(_timestamp: Instant, _printer: PrettyPrinter<EthernetFrame<&[u8]>>) {}
|
||||||
|
|
||||||
let net_trace_fn: fn(u64, PrettyPrinter<EthernetFrame<&[u8]>>);
|
let net_trace_fn: fn(Instant, PrettyPrinter<EthernetFrame<&[u8]>>);
|
||||||
match config::read_str("net_trace", |r| r.map(|s| s == "1")) {
|
match config::read_str("net_trace", |r| r.map(|s| s == "1")) {
|
||||||
Ok(true) => net_trace_fn = net_trace_writer,
|
Ok(true) => net_trace_fn = net_trace_writer,
|
||||||
_ => net_trace_fn = net_trace_silent
|
_ => net_trace_fn = net_trace_silent
|
||||||
|
@ -299,7 +300,8 @@ fn startup_ethernet() {
|
||||||
{
|
{
|
||||||
let sockets = &mut *scheduler.sockets().borrow_mut();
|
let sockets = &mut *scheduler.sockets().borrow_mut();
|
||||||
loop {
|
loop {
|
||||||
match interface.poll(sockets, clock::get_ms()) {
|
let timestamp = smoltcp::time::Instant::from_millis(clock::get_ms() as i64);
|
||||||
|
match interface.poll(sockets, timestamp) {
|
||||||
Ok(true) => (),
|
Ok(true) => (),
|
||||||
Ok(false) => break,
|
Ok(false) => break,
|
||||||
Err(smoltcp::Error::Unrecognized) => (),
|
Err(smoltcp::Error::Unrecognized) => (),
|
||||||
|
@ -373,13 +375,27 @@ pub extern fn abort() {
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle] // https://github.com/rust-lang/rust/issues/{38281,51647}
|
||||||
#[lang = "panic_fmt"]
|
#[lang = "oom"] // https://github.com/rust-lang/rust/issues/51540
|
||||||
pub extern fn panic_fmt(args: core::fmt::Arguments, file: &'static str,
|
pub fn oom(layout: core::alloc::Layout) -> ! {
|
||||||
line: u32, column: u32) -> ! {
|
panic!("heap view: {}\ncannot allocate layout: {:?}", unsafe { &ALLOC }, layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle] // https://github.com/rust-lang/rust/issues/{38281,51647}
|
||||||
|
#[panic_implementation]
|
||||||
|
pub fn panic_impl(info: &core::panic::PanicInfo) -> ! {
|
||||||
irq::set_ie(false);
|
irq::set_ie(false);
|
||||||
|
|
||||||
println!("panic at {}:{}:{}: {}", file, line, column, args);
|
if let Some(location) = info.location() {
|
||||||
|
print!("panic at {}:{}:{}", location.file(), location.line(), location.column());
|
||||||
|
} else {
|
||||||
|
print!("panic at unknown location");
|
||||||
|
}
|
||||||
|
if let Some(message) = info.message() {
|
||||||
|
println!("{}", message);
|
||||||
|
} else {
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
|
||||||
println!("backtrace for software version {}:", csr::CONFIG_IDENTIFIER_STR);
|
println!("backtrace for software version {}:", csr::CONFIG_IDENTIFIER_STR);
|
||||||
let _ = unwind_backtrace::backtrace(|ip| {
|
let _ = unwind_backtrace::backtrace(|ip| {
|
||||||
|
|
|
@ -64,27 +64,23 @@ impl Profile {
|
||||||
|
|
||||||
pub fn record_hit(&mut self, addr: Address) -> Result<(), ()> {
|
pub fn record_hit(&mut self, addr: Address) -> Result<(), ()> {
|
||||||
let mut hits = self.hits();
|
let mut hits = self.hits();
|
||||||
match hits.get_mut(&addr) {
|
if let Some(count) = hits.get_mut(&addr) {
|
||||||
Some(count) => *count = count.saturating_add(1),
|
return Ok(*count = count.saturating_add(1))
|
||||||
None => {
|
|
||||||
if let Err(_) = hits.insert(addr, 1) {
|
|
||||||
return Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
if let Err(_) = hits.insert(addr, 1) {
|
||||||
|
return Err(())
|
||||||
|
}
|
||||||
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn record_edge(&mut self, caller: Address, callee: Address) -> Result<(), ()> {
|
pub fn record_edge(&mut self, caller: Address, callee: Address) -> Result<(), ()> {
|
||||||
let mut edges = self.edges();
|
let mut edges = self.edges();
|
||||||
match edges.get_mut(&(caller, callee)) {
|
if let Some(count) = edges.get_mut(&(caller, callee)) {
|
||||||
Some(count) => *count = count.saturating_add(1),
|
return Ok(*count = count.saturating_add(1))
|
||||||
None => {
|
}
|
||||||
if let Err(_) = edges.insert((caller, callee), 1) {
|
if let Err(_) = edges.insert((caller, callee), 1) {
|
||||||
return Err(())
|
return Err(())
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use core::cell::{Cell, RefCell};
|
||||||
use alloc::Vec;
|
use alloc::Vec;
|
||||||
use fringe::OwnedStack;
|
use fringe::OwnedStack;
|
||||||
use fringe::generator::{Generator, Yielder, State as GeneratorState};
|
use fringe::generator::{Generator, Yielder, State as GeneratorState};
|
||||||
|
use smoltcp::time::Duration;
|
||||||
use smoltcp::Error as NetworkError;
|
use smoltcp::Error as NetworkError;
|
||||||
use smoltcp::wire::IpEndpoint;
|
use smoltcp::wire::IpEndpoint;
|
||||||
use smoltcp::socket::{SocketHandle, SocketRef};
|
use smoltcp::socket::{SocketHandle, SocketRef};
|
||||||
|
@ -426,19 +427,19 @@ impl<'a> TcpStream<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn timeout(&self) -> Option<u64> {
|
pub fn timeout(&self) -> Option<u64> {
|
||||||
self.with_lower(|s| s.timeout())
|
self.with_lower(|s| s.timeout().as_ref().map(Duration::millis))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_timeout(&self, value: Option<u64>) {
|
pub fn set_timeout(&self, value: Option<u64>) {
|
||||||
self.with_lower(|mut s| s.set_timeout(value))
|
self.with_lower(|mut s| s.set_timeout(value.map(Duration::from_millis)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keep_alive(&self) -> Option<u64> {
|
pub fn keep_alive(&self) -> Option<u64> {
|
||||||
self.with_lower(|s| s.keep_alive())
|
self.with_lower(|s| s.keep_alive().as_ref().map(Duration::millis))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_keep_alive(&self, value: Option<u64>) {
|
pub fn set_keep_alive(&self, value: Option<u64>) {
|
||||||
self.with_lower(|mut s| s.set_keep_alive(value))
|
self.with_lower(|mut s| s.set_keep_alive(value.map(Duration::from_millis)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(&self) -> Result<(), Error> {
|
pub fn close(&self) -> Result<(), Error> {
|
||||||
|
|
|
@ -31,7 +31,7 @@ def get_argparser():
|
||||||
verbosity_args(parser)
|
verbosity_args(parser)
|
||||||
|
|
||||||
parser.add_argument("-t", "--target", metavar="TARGET",
|
parser.add_argument("-t", "--target", metavar="TARGET",
|
||||||
type=str, default="kc705",
|
type=str, default="kasli",
|
||||||
help="target to build, one of: "
|
help="target to build, one of: "
|
||||||
"kc705 kasli sayma")
|
"kc705 kasli sayma")
|
||||||
parser.add_argument("-V", "--variant", metavar="VARIANT",
|
parser.add_argument("-V", "--variant", metavar="VARIANT",
|
||||||
|
@ -91,7 +91,7 @@ def main():
|
||||||
variant = "standalone" if args.variant is None else args.variant
|
variant = "standalone" if args.variant is None else args.variant
|
||||||
elif args.target == "kasli":
|
elif args.target == "kasli":
|
||||||
board_type, firmware = "kasli", "runtime"
|
board_type, firmware = "kasli", "runtime"
|
||||||
variant = "opticlock" if args.variant is None else args.variant
|
variant = "tester" if args.variant is None else args.variant
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("unknown target {}".format(args.target))
|
raise NotImplementedError("unknown target {}".format(args.target))
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ class TransferTest(ExperimentCase):
|
||||||
exp = self.create(_Transfer)
|
exp = self.create(_Transfer)
|
||||||
host_to_device_rate = exp.host_to_device()
|
host_to_device_rate = exp.host_to_device()
|
||||||
print(host_to_device_rate, "B/s")
|
print(host_to_device_rate, "B/s")
|
||||||
self.assertGreater(host_to_device_rate, 1.9e6)
|
self.assertGreater(host_to_device_rate, 1.8e6)
|
||||||
|
|
||||||
@unittest.skipUnless(artiq_low_latency,
|
@unittest.skipUnless(artiq_low_latency,
|
||||||
"timings are dependent on CPU load and network conditions")
|
"timings are dependent on CPU load and network conditions")
|
||||||
|
|
|
@ -50,12 +50,17 @@ mod cslice {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[path = "../../firmware/ksupport/eh.rs"]
|
#[path = "."]
|
||||||
pub mod eh;
|
pub mod eh {
|
||||||
|
#[path = "../../firmware/libeh/dwarf.rs"]
|
||||||
|
pub mod dwarf;
|
||||||
|
}
|
||||||
|
#[path = "../../firmware/ksupport/eh_artiq.rs"]
|
||||||
|
pub mod eh_artiq;
|
||||||
|
|
||||||
use std::{str, process};
|
use std::{str, process};
|
||||||
|
|
||||||
fn terminate(exception: &eh::Exception, mut _backtrace: &mut [usize]) -> ! {
|
fn terminate(exception: &eh_artiq::Exception, mut _backtrace: &mut [usize]) -> ! {
|
||||||
println!("Uncaught {}: {} ({}, {}, {})",
|
println!("Uncaught {}: {} ({}, {}, {})",
|
||||||
str::from_utf8(exception.name.as_ref()).unwrap(),
|
str::from_utf8(exception.name.as_ref()).unwrap(),
|
||||||
str::from_utf8(exception.message.as_ref()).unwrap(),
|
str::from_utf8(exception.message.as_ref()).unwrap(),
|
||||||
|
|
|
@ -30,7 +30,7 @@ if os.name == "posix":
|
||||||
support_build = os.path.join(root, "libartiq_support")
|
support_build = os.path.join(root, "libartiq_support")
|
||||||
if subprocess.call(["rustc", os.path.join(support_build, "lib.rs"),
|
if subprocess.call(["rustc", os.path.join(support_build, "lib.rs"),
|
||||||
"--out-dir", support_build,
|
"--out-dir", support_build,
|
||||||
"-Cpanic=abort", "-g"]) != 0:
|
"-Cpanic=unwind", "-g"]) != 0:
|
||||||
lit_config.fatal("Unable to build JIT support library")
|
lit_config.fatal("Unable to build JIT support library")
|
||||||
|
|
||||||
support_lib = os.path.join(support_build, "libartiq_support.so")
|
support_lib = os.path.join(support_build, "libartiq_support.so")
|
||||||
|
|
|
@ -21,7 +21,7 @@ requirements:
|
||||||
- binutils-or1k-linux >=2.27
|
- binutils-or1k-linux >=2.27
|
||||||
- llvm-or1k 6.0.0
|
- llvm-or1k 6.0.0
|
||||||
- llvmlite-artiq 0.23.0.dev py35_4
|
- llvmlite-artiq 0.23.0.dev py35_4
|
||||||
- rust-core-or1k 1.26.0 21
|
- rust-core-or1k 1.28.0 21
|
||||||
- openocd 0.10.0 6
|
- openocd 0.10.0 6
|
||||||
- lit
|
- lit
|
||||||
- outputcheck
|
- outputcheck
|
||||||
|
|
Loading…
Reference in New Issue