Skip to content
15 changes: 11 additions & 4 deletions src/lib/libdylink.js
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ var LibraryDylink = {
name = getString();
}

var customSection = { neededDynlibs: [], tlsExports: new Set(), weakImports: new Set() };
var customSection = { neededDynlibs: [], tlsExports: new Set(), weakImports: new Set(), runtimePaths: [] };
if (name == 'dylink') {
customSection.memorySize = getLEB();
customSection.memoryAlign = getLEB();
Expand All @@ -457,7 +457,7 @@ var LibraryDylink = {
// current module could resolve its imports. (see tools/shared.py
// WebAssembly.make_shared_library() for "dylink" section extension format)
var neededDynlibsCount = getLEB();
for (var i = 0; i < neededDynlibsCount; ++i) {
while (neededDynlibsCount--) {
var libname = getString();
customSection.neededDynlibs.push(libname);
}
Expand All @@ -467,6 +467,7 @@ var LibraryDylink = {
var WASM_DYLINK_NEEDED = 0x2;
var WASM_DYLINK_EXPORT_INFO = 0x3;
var WASM_DYLINK_IMPORT_INFO = 0x4;
var WASM_DYLINK_RUNTIME_PATH = 0x5;
var WASM_SYMBOL_TLS = 0x100;
var WASM_SYMBOL_BINDING_MASK = 0x3;
var WASM_SYMBOL_BINDING_WEAK = 0x1;
Expand All @@ -480,8 +481,8 @@ var LibraryDylink = {
customSection.tableAlign = getLEB();
} else if (subsectionType === WASM_DYLINK_NEEDED) {
var neededDynlibsCount = getLEB();
for (var i = 0; i < neededDynlibsCount; ++i) {
libname = getString();
while (neededDynlibsCount--) {
var libname = getString();
customSection.neededDynlibs.push(libname);
}
} else if (subsectionType === WASM_DYLINK_EXPORT_INFO) {
Expand All @@ -503,6 +504,12 @@ var LibraryDylink = {
customSection.weakImports.add(symname);
}
}
} else if (subsectionType === WASM_DYLINK_RUNTIME_PATH) {
var runtimePathsCount = getLEB();
while (runtimePathsCount--) {
var path = getString();
customSection.runtimePaths.push(path);
}
} else {
#if ASSERTIONS
err(`unknown dylink.0 subsection: ${subsectionType}`)
Expand Down
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_hello_dylink.gzsize
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5907
5911
2 changes: 1 addition & 1 deletion test/other/codesize/test_codesize_hello_dylink.jssize
Original file line number Diff line number Diff line change
@@ -1 +1 @@
13048
13085
54 changes: 50 additions & 4 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -2357,6 +2357,55 @@ def test_dylink_dependencies(self):
self.run_process(cmd + ['-L.'])
self.run_js('a.out.js')

def test_dylink_dependencies_rpath(self):
create_file('side1.c', r'''
#include <stdio.h>
#include <stdlib.h>

void side2();

void side1() {
printf("side1\n");
side2();
}
''')
create_file('side2.c', r'''
#include <stdio.h>
#include <stdlib.h>

void side2() {
printf("side2\n");
}
''')
create_file('main.c', '''
void side1();

int main() {
side1();
return 0;
}
''')
self.emcc('side2.c', ['-fPIC', '-sSIDE_MODULE', '-olibside2.so'])
self.emcc('side1.c', ['-fPIC', '-sSIDE_MODULE', '-Wl,-rpath,$ORIGIN', '-olibside1.so', 'libside2.so'])
cmd = [EMCC, 'main.c', '-fPIC', '-sMAIN_MODULE=2', 'libside1.so']

# Unless `.` is added to the library path the libside2.so won't be found.
err = self.expect_fail(cmd)
self.assertContained('emcc: error: libside1.so: shared library dependency not found in library path: `libside2.so`.', err)

# Adding -L. to the library path makes it work.
self.run_process(cmd + ['-L.', '-Wl,-rpath,$ORIGIN'])
self.run_js('a.out.js')

def get_runtime_paths(path):
with webassembly.Module(path) as module:
dylink_section = module.parse_dylink_section()
return dylink_section.runtime_paths

self.assertEqual(get_runtime_paths('libside2.so'), [])
self.assertEqual(get_runtime_paths('libside1.so'), ['$ORIGIN'])
self.assertEqual(get_runtime_paths('a.out.wasm'), ['$ORIGIN'])

def test_dylink_LEGACY_GL_EMULATION(self):
# LEGACY_GL_EMULATION wraps JS library functions. This test ensure that when it does
# so it preserves the `.sig` attributes needed by dynamic linking.
Expand Down Expand Up @@ -12415,9 +12464,6 @@ def test_missing_stdlibs(self):
self.run_process([EMCC, test_file('hello_world.c'), '-lm', '-ldl', '-lrt', '-lpthread'])

def test_supported_linker_flags(self):
out = self.run_process([EMCC, test_file('hello_world.c'), '-Wl,-rpath=foo'], stderr=PIPE).stderr
self.assertContained('warning: ignoring unsupported linker flag: `-rpath=foo`', out)

out = self.run_process([EMCC, test_file('hello_world.c'), '-Wl,-rpath-link,foo'], stderr=PIPE).stderr
self.assertContained('warning: ignoring unsupported linker flag: `-rpath-link`', out)

Expand All @@ -12437,7 +12483,7 @@ def test_supported_linker_flags(self):
def test_supported_linker_flag_skip_next(self):
# Regression test for a bug where skipping an unsupported linker flag
# could skip the next unrelated linker flag.
err = self.expect_fail([EMCC, test_file('hello_world.c'), '-Wl,-rpath=foo', '-lbar'])
err = self.expect_fail([EMCC, test_file('hello_world.c'), '-Wl,-version-script,foo', '-lbar'])
self.assertContained('error: unable to find library -lbar', err)

def test_linker_flags_pass_through(self):
Expand Down
4 changes: 2 additions & 2 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
'--start-group', '--end-group',
'-(', '-)',
'--whole-archive', '--no-whole-archive',
'-whole-archive', '-no-whole-archive'
'-whole-archive', '-no-whole-archive',
'-rpath',
)

# Unsupported LLD flags which we will ignore.
Expand All @@ -72,7 +73,6 @@
# wasm-ld doesn't support soname or other dynamic linking flags (yet). Ignore them
# in order to aid build systems that want to pass these flags.
'-allow-shlib-undefined': False,
'-rpath': True,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should also add this to SUPPORTED_LINKER_FLAGS above?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not? @sbc100 it's not entirely clear to me from reading the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I added -rpath there.

'-rpath-link': True,
'-version-script': True,
'-install_name': True,
Expand Down
12 changes: 10 additions & 2 deletions tools/webassembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ class DylinkType(IntEnum):
NEEDED = 2
EXPORT_INFO = 3
IMPORT_INFO = 4
RUNTIME_PATH = 5


class TargetFeaturePrefix(IntEnum):
Expand All @@ -165,7 +166,7 @@ class InvalidWasmError(BaseException):
Import = namedtuple('Import', ['kind', 'module', 'field', 'type'])
Export = namedtuple('Export', ['name', 'kind', 'index'])
Global = namedtuple('Global', ['type', 'mutable', 'init'])
Dylink = namedtuple('Dylink', ['mem_size', 'mem_align', 'table_size', 'table_align', 'needed', 'export_info', 'import_info'])
Dylink = namedtuple('Dylink', ['mem_size', 'mem_align', 'table_size', 'table_align', 'needed', 'export_info', 'import_info', 'runtime_paths'])
Table = namedtuple('Table', ['elem_type', 'limits'])
FunctionBody = namedtuple('FunctionBody', ['offset', 'size'])
DataSegment = namedtuple('DataSegment', ['flags', 'init', 'offset', 'size'])
Expand Down Expand Up @@ -313,6 +314,7 @@ def parse_dylink_section(self):
needed = []
export_info = {}
import_info = {}
runtime_paths = []
self.read_string() # name

if dylink_section.name == 'dylink':
Expand Down Expand Up @@ -359,6 +361,12 @@ def parse_dylink_section(self):
import_info.setdefault(module, {})
import_info[module][field] = flags
count -= 1
elif subsection_type == DylinkType.RUNTIME_PATH:
count = self.read_uleb()
while count:
rpath = self.read_string()
runtime_paths.append(rpath)
count -= 1
else:
print(f'unknown subsection: {subsection_type}')
# ignore unknown subsections
Expand All @@ -367,7 +375,7 @@ def parse_dylink_section(self):
else:
utils.exit_with_error('error parsing shared library')

return Dylink(mem_size, mem_align, table_size, table_align, needed, export_info, import_info)
return Dylink(mem_size, mem_align, table_size, table_align, needed, export_info, import_info, runtime_paths)

@memoize
def get_exports(self):
Expand Down