Reduced executable size on Linux
All checks were successful
/ test (push) Successful in 29s

This commit is contained in:
Eduard Urbach 2025-07-04 09:09:22 +02:00
parent ca9595d1fb
commit 36f76170f5
Signed by: akyoto
GPG key ID: 49226B848C78F6C8
9 changed files with 48 additions and 20 deletions

View file

@ -59,8 +59,7 @@ func (a *Assembler) Compile(b *build.Build) (code []byte, data []byte, libs dll.
} }
} }
x := exe.New(elf.HeaderEnd, b.FileAlign(), b.MemoryAlign()) x := exe.New(elf.HeaderEnd, b.FileAlign(), b.MemoryAlign(), b.Congruent(), c.code, c.data, nil)
x.InitSections(c.code, c.data, nil)
dataSectionOffset := x.Sections[1].MemoryOffset - x.Sections[0].MemoryOffset dataSectionOffset := x.Sections[1].MemoryOffset - x.Sections[0].MemoryOffset
for dataLabel, address := range dataLabels { for dataLabel, address := range dataLabels {

6
src/build/Congruent.go Normal file
View file

@ -0,0 +1,6 @@
package build
// Congruent returns true if the platform needs to force `virtual address % alignment == file offset % alignment`.
func (build *Build) Congruent() bool {
return build.OS == Linux
}

View file

@ -1,6 +1,13 @@
package build package build
// cacheLineSize is the smallest unit of data that can be transferred between the RAM and the CPU cache.
const cacheLineSize = 64
// FileAlign returns the file alignment. // FileAlign returns the file alignment.
func (build *Build) FileAlign() int { func (build *Build) FileAlign() int {
if build.OS == Linux {
return cacheLineSize
}
return build.MemoryAlign() return build.MemoryAlign()
} }

View file

@ -4,7 +4,7 @@ package build
func (build *Build) MemoryAlign() int { func (build *Build) MemoryAlign() int {
switch build.Arch { switch build.Arch {
case ARM: case ARM:
return 0x4000 return 0x10000
default: default:
return 0x1000 return 0x1000
} }

View file

@ -18,8 +18,7 @@ type ELF struct {
// Write writes the ELF64 format to the given writer. // Write writes the ELF64 format to the given writer.
func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []byte) { func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []byte) {
x := exe.New(HeaderEnd, b.FileAlign(), b.MemoryAlign()) x := exe.New(HeaderEnd, b.FileAlign(), b.MemoryAlign(), b.Congruent(), codeBytes, dataBytes)
x.InitSections(codeBytes, dataBytes)
code := x.Sections[0] code := x.Sections[0]
data := x.Sections[1] data := x.Sections[1]

View file

@ -6,26 +6,25 @@ type Executable struct {
headerEnd int headerEnd int
fileAlign int fileAlign int
memoryAlign int memoryAlign int
congruent bool
} }
// New creates a new executable. // New creates a new executable.
func New(headerEnd int, fileAlign int, memoryAlign int) *Executable { func New(headerEnd int, fileAlign int, memoryAlign int, congruent bool, raw ...[]byte) *Executable {
return &Executable{ exe := &Executable{
Sections: make([]*Section, len(raw)),
headerEnd: headerEnd, headerEnd: headerEnd,
fileAlign: fileAlign, fileAlign: fileAlign,
memoryAlign: memoryAlign, memoryAlign: memoryAlign,
congruent: congruent,
} }
}
// InitSections generates sections from raw byte slices.
func (exe *Executable) InitSections(raw ...[]byte) {
exe.Sections = make([]*Section, len(raw))
for i, data := range raw { for i, data := range raw {
exe.Sections[i] = &Section{Bytes: data} exe.Sections[i] = &Section{Bytes: data}
} }
exe.Update() exe.Update()
return exe
} }
// Update recalculates all section offsets. // Update recalculates all section offsets.
@ -34,9 +33,17 @@ func (exe *Executable) Update() {
first.FileOffset, first.Padding = AlignPad(exe.headerEnd, exe.fileAlign) first.FileOffset, first.Padding = AlignPad(exe.headerEnd, exe.fileAlign)
first.MemoryOffset = Align(exe.headerEnd, exe.memoryAlign) first.MemoryOffset = Align(exe.headerEnd, exe.memoryAlign)
if exe.congruent && exe.fileAlign != exe.memoryAlign {
first.MemoryOffset += first.FileOffset % exe.memoryAlign
}
for i, section := range exe.Sections[1:] { for i, section := range exe.Sections[1:] {
previous := exe.Sections[i] previous := exe.Sections[i]
section.FileOffset, section.Padding = AlignPad(previous.FileOffset+len(previous.Bytes), exe.fileAlign) section.FileOffset, section.Padding = AlignPad(previous.FileOffset+len(previous.Bytes), exe.fileAlign)
section.MemoryOffset = Align(previous.MemoryOffset+len(previous.Bytes), exe.memoryAlign) section.MemoryOffset = Align(previous.MemoryOffset+len(previous.Bytes), exe.memoryAlign)
if exe.congruent && exe.fileAlign != exe.memoryAlign {
section.MemoryOffset += section.FileOffset % exe.memoryAlign
}
} }
} }

View file

@ -7,13 +7,25 @@ import (
"git.urbach.dev/go/assert" "git.urbach.dev/go/assert"
) )
func TestExecutable(t *testing.T) { func TestSimple(t *testing.T) {
align := 0x1000 align := 32
x := exe.New(1, align, align) x := exe.New(1, align, align, false, []byte{1}, []byte{1})
x.InitSections([]byte{1}, []byte{1})
assert.Equal(t, len(x.Sections), 2) assert.Equal(t, len(x.Sections), 2)
assert.Equal(t, x.Sections[0].Padding, align-1) assert.Equal(t, x.Sections[0].Padding, align-1)
assert.Equal(t, x.Sections[0].FileOffset, align) assert.Equal(t, x.Sections[0].FileOffset, align)
assert.Equal(t, x.Sections[1].Padding, align-1) assert.Equal(t, x.Sections[1].Padding, align-1)
assert.Equal(t, x.Sections[1].FileOffset, align*2) assert.Equal(t, x.Sections[1].FileOffset, align*2)
}
func TestCongruent(t *testing.T) {
fileAlign := 16
memoryAlign := 32
x := exe.New(1, fileAlign, memoryAlign, true, []byte{1}, []byte{1}, []byte{1})
assert.Equal(t, len(x.Sections), 3)
assert.Equal(t, x.Sections[0].FileOffset, fileAlign)
assert.Equal(t, x.Sections[1].FileOffset, fileAlign*2)
assert.Equal(t, x.Sections[2].FileOffset, fileAlign*3)
assert.Equal(t, x.Sections[0].MemoryOffset, memoryAlign+(fileAlign%memoryAlign))
assert.Equal(t, x.Sections[1].MemoryOffset, memoryAlign*2+(fileAlign*2%memoryAlign))
assert.Equal(t, x.Sections[2].MemoryOffset, memoryAlign*3+(fileAlign*3%memoryAlign))
} }

View file

@ -19,8 +19,7 @@ type MachO struct {
// Write writes the Mach-O format to the given writer. // Write writes the Mach-O format to the given writer.
func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []byte) { func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []byte) {
x := exe.New(HeaderEnd, b.FileAlign(), b.MemoryAlign()) x := exe.New(HeaderEnd, b.FileAlign(), b.MemoryAlign(), b.Congruent(), codeBytes, dataBytes)
x.InitSections(codeBytes, dataBytes)
code := x.Sections[0] code := x.Sections[0]
data := x.Sections[1] data := x.Sections[1]
arch, microArch := Arch(b.Arch) arch, microArch := Arch(b.Arch)

View file

@ -20,8 +20,7 @@ type EXE struct {
// Write writes the EXE file to the given writer. // Write writes the EXE file to the given writer.
func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []byte, libs dll.List) { func Write(writer io.WriteSeeker, b *build.Build, codeBytes []byte, dataBytes []byte, libs dll.List) {
x := exe.New(HeaderEnd, b.FileAlign(), b.MemoryAlign()) x := exe.New(HeaderEnd, b.FileAlign(), b.MemoryAlign(), b.Congruent(), codeBytes, dataBytes, nil)
x.InitSections(codeBytes, dataBytes, nil)
code := x.Sections[0] code := x.Sections[0]
data := x.Sections[1] data := x.Sections[1]
imports := x.Sections[2] imports := x.Sections[2]