Improve use of data section for RW data
8a64c0a3cca95edb4c6e508e9f6ab5b5cef2f5735b68a14b39c7b06901bb381b
Previously we weren't taking all opportunities to use BSS over RW data. Now we check aggregates for zero-ness.
1 parent
364f0a91
compiler/radiance.rad
+1 -4
| 752 | 752 | ||
| 753 | 753 | return unix::writeFile(path, bytes); |
|
| 754 | 754 | } |
|
| 755 | 755 | ||
| 756 | 756 | /// Write a data section to a file at `basePath` + `ext`. |
|
| 757 | - | /// If the data is empty, no file is written. |
|
| 757 | + | /// Empty data truncates any stale sidecar left by an earlier build. |
|
| 758 | 758 | fn writeDataWithExt( |
|
| 759 | 759 | data: *[u8], |
|
| 760 | 760 | basePath: *[u8], |
|
| 761 | 761 | ext: *[u8] |
|
| 762 | 762 | ) throws (Error) { |
|
| 763 | - | if data.len == 0 { |
|
| 764 | - | return; |
|
| 765 | - | } |
|
| 766 | 763 | let mut path: [u8; MAX_PATH_LEN] = undefined; |
|
| 767 | 764 | let mut pos: u32 = 0; |
|
| 768 | 765 | ||
| 769 | 766 | set pos += try! mem::copy(&mut path[pos..], basePath); |
|
| 770 | 767 | set pos += try! mem::copy(&mut path[pos..], ext); |
lib/std/lang/gen/data.rad
+8 -8
| 30 | 30 | /// Fallback linear array for edge cases. |
|
| 31 | 31 | syms: *[DataSym], |
|
| 32 | 32 | } |
|
| 33 | 33 | ||
| 34 | 34 | /// Lay out data symbols for a single section. |
|
| 35 | - | /// Initialized data is placed first, then uninitialized, so that only |
|
| 36 | - | /// initialized data needs to be written to the output file. |
|
| 35 | + | /// Data with sidecar image bytes is placed first, then zero-initialized data, |
|
| 36 | + | /// so that only meaningful bytes need to be written to the output file. |
|
| 37 | 37 | /// Returns the updated offset past all placed symbols. |
|
| 38 | 38 | export fn layoutSection( |
|
| 39 | 39 | items: *[il::Data], |
|
| 40 | 40 | syms: *mut [DataSym], |
|
| 41 | 41 | count: *mut u32, |
| 54 | 54 | startOffset: u32, |
|
| 55 | 55 | readOnly: bool |
|
| 56 | 56 | ) -> u32 { |
|
| 57 | 57 | let mut offset: u32 = startOffset; |
|
| 58 | 58 | ||
| 59 | - | // Initialized data first. |
|
| 59 | + | // Data requiring sidecar image bytes first. |
|
| 60 | 60 | for i in 0..items.len { |
|
| 61 | 61 | let data = &items[i]; |
|
| 62 | - | if data.readOnly == readOnly and not data.isUndefined { |
|
| 62 | + | if data.readOnly == readOnly and not data.isZeroInit { |
|
| 63 | 63 | set offset = mem::alignUp(offset, data.alignment); |
|
| 64 | 64 | set syms[*count] = DataSym { name: data.name, addr: base + offset }; |
|
| 65 | 65 | set *count += 1; |
|
| 66 | 66 | set offset += data.size; |
|
| 67 | 67 | } |
|
| 68 | 68 | } |
|
| 69 | - | // Uninitialized data after. |
|
| 69 | + | // Zero-initialized data after. |
|
| 70 | 70 | for i in 0..items.len { |
|
| 71 | 71 | let data = &items[i]; |
|
| 72 | - | if data.readOnly == readOnly and data.isUndefined { |
|
| 72 | + | if data.readOnly == readOnly and data.isZeroInit { |
|
| 73 | 73 | set offset = mem::alignUp(offset, data.alignment); |
|
| 74 | 74 | set syms[*count] = DataSym { name: data.name, addr: base + offset }; |
|
| 75 | 75 | set *count += 1; |
|
| 76 | 76 | set offset += data.size; |
|
| 77 | 77 | } |
|
| 78 | 78 | } |
|
| 79 | 79 | return offset; |
|
| 80 | 80 | } |
|
| 81 | 81 | ||
| 82 | 82 | /// Emit data bytes for a single section (read-only or read-write) into `buf`. |
|
| 83 | - | /// Iterates initialized data in the IL program, serializing each data item. |
|
| 83 | + | /// Iterates data requiring sidecar image bytes, serializing each data item. |
|
| 84 | 84 | /// Returns the total number of bytes written. |
|
| 85 | 85 | export fn emitSection( |
|
| 86 | 86 | items: *[il::Data], |
|
| 87 | 87 | dataSymMap: *DataSymMap, |
|
| 88 | 88 | fnLabels: *labels::Labels, |
| 105 | 105 | ) -> u32 { |
|
| 106 | 106 | let mut offset: u32 = startOffset; |
|
| 107 | 107 | ||
| 108 | 108 | for i in 0..items.len { |
|
| 109 | 109 | let data = &items[i]; |
|
| 110 | - | if data.readOnly == readOnly and not data.isUndefined { |
|
| 110 | + | if data.readOnly == readOnly and not data.isZeroInit { |
|
| 111 | 111 | set offset = mem::alignUp(offset, data.alignment); |
|
| 112 | 112 | assert offset + data.size <= buf.len, "emitSectionAtOffset: buffer overflow"; |
|
| 113 | 113 | for j in 0..data.values.len { |
|
| 114 | 114 | let v = &data.values[j]; |
|
| 115 | 115 | for _ in 0..v.count { |
lib/std/lang/il.rad
+3 -3
| 376 | 376 | alignment: u32, |
|
| 377 | 377 | /// Whether this is read-only data. |
|
| 378 | 378 | /// Typically, `constant` declaration are read-only, while `static` |
|
| 379 | 379 | /// declarations are not. |
|
| 380 | 380 | readOnly: bool, |
|
| 381 | - | /// Whether the data is entirely undefined. |
|
| 382 | - | /// Undefined data doesn't need to be written since memory is zero-initialized. |
|
| 383 | - | isUndefined: bool, |
|
| 381 | + | /// Whether writable data can be represented by zero-filled memory. |
|
| 382 | + | /// Zero-initialized data doesn't need to be written to the sidecar image. |
|
| 383 | + | isZeroInit: bool, |
|
| 384 | 384 | /// Initializer values. |
|
| 385 | 385 | values: *[DataValue], |
|
| 386 | 386 | } |
|
| 387 | 387 | ||
| 388 | 388 | /// An IL program (compilation unit). |
lib/std/lang/lower.rad
+52 -12
| 379 | 379 | ||
| 380 | 380 | /// Builder for accumulating data values during constant lowering. |
|
| 381 | 381 | record DataValueBuilder { |
|
| 382 | 382 | allocator: alloc::Allocator, |
|
| 383 | 383 | values: *mut [il::DataValue], |
|
| 384 | - | /// Whether all values pushed are undefined. |
|
| 385 | - | allUndef: bool, |
|
| 384 | + | /// Whether all pushed values can be represented by zero-filled memory. |
|
| 385 | + | zeroInit: bool, |
|
| 386 | 386 | } |
|
| 387 | 387 | ||
| 388 | 388 | /// Result of lowering constant data. |
|
| 389 | 389 | record ConstDataResult { |
|
| 390 | 390 | values: *[il::DataValue], |
|
| 391 | - | isUndefined: bool, |
|
| 391 | + | zeroInit: bool, |
|
| 392 | 392 | } |
|
| 393 | 393 | ||
| 394 | 394 | /// Create a new builder. |
|
| 395 | 395 | fn dataBuilder(allocator: alloc::Allocator) -> DataValueBuilder { |
|
| 396 | - | return DataValueBuilder { allocator, values: &mut [], allUndef: true }; |
|
| 396 | + | return DataValueBuilder { allocator, values: &mut [], zeroInit: true }; |
|
| 397 | + | } |
|
| 398 | + | ||
| 399 | + | /// Return whether a data item can be omitted from a zero-filled image. |
|
| 400 | + | fn dataIsZero(item: il::DataItem) -> bool { |
|
| 401 | + | match item { |
|
| 402 | + | case il::DataItem::Undef => return true, |
|
| 403 | + | case il::DataItem::Val { val, .. } => return val == 0, |
|
| 404 | + | else => return false, |
|
| 405 | + | } |
|
| 406 | + | } |
|
| 407 | + | ||
| 408 | + | /// Return whether all data values can be omitted from a zero-filled image. |
|
| 409 | + | fn dataValuesAreZeroInit(values: *[il::DataValue]) -> bool { |
|
| 410 | + | for value in values { |
|
| 411 | + | if value.count > 0 and not dataIsZero(value.item) { |
|
| 412 | + | return false; |
|
| 413 | + | } |
|
| 414 | + | } |
|
| 415 | + | return true; |
|
| 397 | 416 | } |
|
| 398 | 417 | ||
| 399 | 418 | /// Append a data value to the builder. |
|
| 400 | 419 | fn dataBuilderPush(b: *mut DataValueBuilder, value: il::DataValue) { |
|
| 401 | 420 | b.values.append(value, b.allocator); |
|
| 402 | 421 | ||
| 403 | - | if value.item <> il::DataItem::Undef { |
|
| 404 | - | set b.allUndef = false; |
|
| 422 | + | if value.count > 0 and not dataIsZero(value.item) { |
|
| 423 | + | set b.zeroInit = false; |
|
| 405 | 424 | } |
|
| 406 | 425 | } |
|
| 407 | 426 | ||
| 408 | 427 | /// Return the accumulated values. |
|
| 409 | 428 | fn dataBuilderFinish(b: *DataValueBuilder) -> ConstDataResult { |
|
| 410 | 429 | return ConstDataResult { |
|
| 411 | 430 | values: &b.values[..], |
|
| 412 | - | isUndefined: b.allUndef, |
|
| 431 | + | zeroInit: b.zeroInit, |
|
| 413 | 432 | }; |
|
| 414 | 433 | } |
|
| 415 | 434 | ||
| 416 | 435 | /////////////////////////// |
|
| 417 | 436 | // SSA Variable Tracking // |
| 1116 | 1135 | self.data.append(il::Data { |
|
| 1117 | 1136 | name: vName, |
|
| 1118 | 1137 | size: traitInfo.methods.len as u32 * resolver::PTR_SIZE, |
|
| 1119 | 1138 | alignment: resolver::PTR_SIZE, |
|
| 1120 | 1139 | readOnly: true, |
|
| 1121 | - | isUndefined: false, |
|
| 1140 | + | isZeroInit: false, |
|
| 1122 | 1141 | values: &values[..traitInfo.methods.len as u32], |
|
| 1123 | 1142 | }, self.allocator); |
|
| 1124 | 1143 | } |
|
| 1125 | 1144 | ||
| 1126 | 1145 | /// Lower a method node into an IL function with the given qualified name. |
| 1324 | 1343 | self.data.append(il::Data { |
|
| 1325 | 1344 | name: qualName, |
|
| 1326 | 1345 | size: layout.size, |
|
| 1327 | 1346 | alignment: layout.alignment, |
|
| 1328 | 1347 | readOnly, |
|
| 1329 | - | isUndefined: result.isUndefined, |
|
| 1348 | + | isZeroInit: not readOnly and result.zeroInit, |
|
| 1330 | 1349 | values: result.values, |
|
| 1331 | 1350 | }, self.allocator); |
|
| 1332 | 1351 | } |
|
| 1333 | 1352 | ||
| 1334 | 1353 | /// Emit the in-memory representation of a slice header: `{ ptr, len, cap }`. |
| 1717 | 1736 | readOnly: bool, |
|
| 1718 | 1737 | values: *[il::DataValue], |
|
| 1719 | 1738 | dataPrefix: *[u8] |
|
| 1720 | 1739 | ) -> *[u8] throws (LowerError) { |
|
| 1721 | 1740 | let name = try nextDeclDataName(self, dataPrefix, self.data.len, "literal"); |
|
| 1722 | - | self.data.append(il::Data { name, size, alignment, readOnly, isUndefined: false, values }, self.allocator); |
|
| 1741 | + | self.data.append(il::Data { |
|
| 1742 | + | name, |
|
| 1743 | + | size, |
|
| 1744 | + | alignment, |
|
| 1745 | + | readOnly, |
|
| 1746 | + | isZeroInit: not readOnly and dataValuesAreZeroInit(values), |
|
| 1747 | + | values, |
|
| 1748 | + | }, self.allocator); |
|
| 1723 | 1749 | ||
| 1724 | 1750 | return name; |
|
| 1725 | 1751 | } |
|
| 1726 | 1752 | ||
| 1727 | 1753 | /// Find or create read-only string data and return its symbol name. |
| 1838 | 1864 | if readOnly { |
|
| 1839 | 1865 | if let found = findConstData(self.low, values, alignment) { |
|
| 1840 | 1866 | set dataName = found; |
|
| 1841 | 1867 | } else { |
|
| 1842 | 1868 | set dataName = try nextDataName(self); |
|
| 1843 | - | self.low.data.append(il::Data { name: dataName, size, alignment, readOnly, isUndefined: false, values }, self.low.allocator); |
|
| 1869 | + | self.low.data.append(il::Data { |
|
| 1870 | + | name: dataName, |
|
| 1871 | + | size, |
|
| 1872 | + | alignment, |
|
| 1873 | + | readOnly, |
|
| 1874 | + | isZeroInit: false, |
|
| 1875 | + | values, |
|
| 1876 | + | }, self.low.allocator); |
|
| 1844 | 1877 | } |
|
| 1845 | 1878 | } else { |
|
| 1846 | 1879 | set dataName = try nextDataName(self); |
|
| 1847 | - | self.low.data.append(il::Data { name: dataName, size, alignment, readOnly, isUndefined: false, values }, self.low.allocator); |
|
| 1880 | + | self.low.data.append(il::Data { |
|
| 1881 | + | name: dataName, |
|
| 1882 | + | size, |
|
| 1883 | + | alignment, |
|
| 1884 | + | readOnly, |
|
| 1885 | + | isZeroInit: dataValuesAreZeroInit(values), |
|
| 1886 | + | values, |
|
| 1887 | + | }, self.low.allocator); |
|
| 1848 | 1888 | } |
|
| 1849 | 1889 | ||
| 1850 | 1890 | // Get data address. |
|
| 1851 | 1891 | let ptrReg = nextReg(self); |
|
| 1852 | 1892 | emit(self, il::Instr::Copy { dst: ptrReg, val: il::Val::DataSym(dataName) }); |
test/run
+28 -0
| 8 | 8 | # For each test: |
|
| 9 | 9 | # - If a `.ril` file exists alongside it, the IL output is checked |
|
| 10 | 10 | # against it via the runner binary. |
|
| 11 | 11 | # - If `//! returns: N` appears in the file, the test is compiled to |
|
| 12 | 12 | # a binary and executed; the exit code must match N. |
|
| 13 | + | # - If `//! rw-data-size: N` appears in the file, the emitted `.rw.data` |
|
| 14 | + | # sidecar size must match N bytes. Missing sidecars count as zero bytes. |
|
| 13 | 15 | ||
| 14 | 16 | RUNNER="test/runner.rv64" |
|
| 15 | 17 | TEST_DIR="test/tests" |
|
| 16 | 18 | EMU="${RAD_EMULATOR:-emulator} -stack-size=1024 -run" |
|
| 17 | 19 | EMU_RUN="${RAD_EMULATOR:-emulator} -no-jit -run" |
| 47 | 49 | *) base="$test" ;; |
|
| 48 | 50 | esac |
|
| 49 | 51 | ||
| 50 | 52 | ril="${base}.ril" |
|
| 51 | 53 | bin="${base}.rv64" |
|
| 54 | + | rw_data="${bin}.rw.data" |
|
| 52 | 55 | ||
| 53 | 56 | # IL check: run the runner if a .ril file exists. |
|
| 54 | 57 | if [ -f "$ril" ]; then |
|
| 55 | 58 | if $EMU "$RUNNER" -- "$test"; then |
|
| 56 | 59 | passed=$((passed + 1)) |
| 79 | 82 | else |
|
| 80 | 83 | echo "FAILED (expected: $returns, got: $ret)" |
|
| 81 | 84 | failed=$((failed + 1)) |
|
| 82 | 85 | fi |
|
| 83 | 86 | fi |
|
| 87 | + | ||
| 88 | + | rw_data_size=$(grep -m1 '^//! rw-data-size:' "$test" | sed 's/^\/\/! rw-data-size: *//') |
|
| 89 | + | if [ -n "$rw_data_size" ]; then |
|
| 90 | + | echo -n "test $test rw.data ... " |
|
| 91 | + | ||
| 92 | + | if [ ! -f "$bin" ]; then |
|
| 93 | + | echo "FAILED (binary not found: $bin)" |
|
| 94 | + | failed=$((failed + 1)) |
|
| 95 | + | continue |
|
| 96 | + | fi |
|
| 97 | + | ||
| 98 | + | if [ -f "$rw_data" ]; then |
|
| 99 | + | actual_rw_data_size=$(wc -c < "$rw_data" | tr -d ' ') |
|
| 100 | + | else |
|
| 101 | + | actual_rw_data_size=0 |
|
| 102 | + | fi |
|
| 103 | + | ||
| 104 | + | if [ "$actual_rw_data_size" -eq "$rw_data_size" ]; then |
|
| 105 | + | echo "ok" |
|
| 106 | + | passed=$((passed + 1)) |
|
| 107 | + | else |
|
| 108 | + | echo "FAILED (expected: $rw_data_size, got: $actual_rw_data_size)" |
|
| 109 | + | failed=$((failed + 1)) |
|
| 110 | + | fi |
|
| 111 | + | fi |
|
| 84 | 112 | done |
|
| 85 | 113 | echo |
|
| 86 | 114 | ||
| 87 | 115 | if [ "$failed" -eq 0 ]; then |
|
| 88 | 116 | echo "test result: ok. $passed passed; $failed failed" |
test/tests/static.zero.bss.rad
added
+45 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! rw-data-size: 0 |
|
| 3 | + | ||
| 4 | + | /// Static pool used to verify that partially initialized zero data is BSS. |
|
| 5 | + | record Pool { |
|
| 6 | + | /// Undefined slice table. |
|
| 7 | + | table: [*[u8]; 32], |
|
| 8 | + | /// Explicitly initialized count. |
|
| 9 | + | count: u32, |
|
| 10 | + | } |
|
| 11 | + | ||
| 12 | + | /// Padded record used to verify that zero fields plus undefined padding are BSS. |
|
| 13 | + | record Padded { |
|
| 14 | + | /// Leading byte. |
|
| 15 | + | tag: u8, |
|
| 16 | + | /// Field that forces padding after `tag`. |
|
| 17 | + | value: u32, |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// Partially initialized aggregate where all concrete bytes are zero. |
|
| 21 | + | static POOL: Pool = Pool { table: undefined, count: 0 }; |
|
| 22 | + | ||
| 23 | + | /// Repeated scalar zeros. |
|
| 24 | + | static FLAGS: [bool; 4] = [false; 4]; |
|
| 25 | + | ||
| 26 | + | /// Explicit zero fields with undefined padding. |
|
| 27 | + | static PADDED: Padded = Padded { tag: 0, value: 0 }; |
|
| 28 | + | ||
| 29 | + | @default fn main() -> i32 { |
|
| 30 | + | assert POOL.count == 0; |
|
| 31 | + | assert FLAGS[0] == false; |
|
| 32 | + | assert FLAGS[3] == false; |
|
| 33 | + | assert PADDED.tag == 0; |
|
| 34 | + | assert PADDED.value == 0; |
|
| 35 | + | ||
| 36 | + | set POOL.count = 3; |
|
| 37 | + | set FLAGS[2] = true; |
|
| 38 | + | set PADDED.value = 9; |
|
| 39 | + | ||
| 40 | + | assert POOL.count == 3; |
|
| 41 | + | assert FLAGS[2] == true; |
|
| 42 | + | assert PADDED.value == 9; |
|
| 43 | + | ||
| 44 | + | return 0; |
|
| 45 | + | } |