Skip to content

Commit 0c31ab7

Browse files
authored
Merge pull request #1663 from tweag/fd/narrowing-th
Narrowing of module dependencies when using TemplateHaskell
2 parents b58cfa3 + 3153d04 commit 0c31ab7

File tree

19 files changed

+399
-31
lines changed

19 files changed

+399
-31
lines changed

haskell/cabal.bzl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ def _haskell_cabal_library_impl(ctx):
573573
)
574574
hs_info = HaskellInfo(
575575
package_databases = depset([package_database], transitive = [dep_info.package_databases]),
576+
empty_lib_package_databases = depset(transitive = [dep_info.empty_lib_package_databases]),
576577
version_macros = set.empty(),
577578
source_files = depset(),
578579
boot_files = depset(),
@@ -583,6 +584,7 @@ def _haskell_cabal_library_impl(ctx):
583584
transitive = [dep_info.hs_libraries],
584585
order = "topological",
585586
),
587+
empty_hs_libraries = dep_info.empty_hs_libraries,
586588
interface_dirs = depset([interfaces_dir], transitive = [dep_info.interface_dirs]),
587589
compile_flags = [],
588590
user_compile_flags = [],
@@ -864,12 +866,14 @@ def _haskell_cabal_binary_impl(ctx):
864866

865867
hs_info = HaskellInfo(
866868
package_databases = dep_info.package_databases,
869+
empty_lib_package_databases = dep_info.empty_lib_package_databases,
867870
version_macros = set.empty(),
868871
source_files = depset(),
869872
boot_files = depset(),
870873
extra_source_files = depset(),
871874
import_dirs = set.empty(),
872875
hs_libraries = dep_info.hs_libraries,
876+
empty_hs_libraries = dep_info.empty_hs_libraries,
873877
interface_dirs = dep_info.interface_dirs,
874878
compile_flags = [],
875879
user_compile_flags = [],

haskell/experimental/defs.bzl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ _haskell_module = rule(
2626
),
2727
"deps": attr.label_list(),
2828
"cross_library_deps": attr.label_list(),
29+
"enable_th": attr.bool(),
2930
"ghcopts": attr.string_list(),
3031
"plugins": attr.label_list(
3132
aspects = [haskell_cc_libraries_aspect],
@@ -59,6 +60,7 @@ def haskell_module(
5960
module_name = "",
6061
deps = [],
6162
cross_library_deps = [],
63+
enable_th = False,
6264
ghcopts = [],
6365
plugins = [],
6466
tools = [],
@@ -124,6 +126,9 @@ def haskell_module(
124126
cross_library_deps: List of other Haskell modules needed to compile this module that come from other libraries.
125127
They need to be included in the `modules` attribute of any library in the `narrowed_deps` attribute
126128
of the enclosing library, binary, or test
129+
enable_th: Exposes object files or libraries to the build action. This is necessary when the module uses
130+
Template Haskell. The libraries of narrowed deps are exposed instead of object files in profiling
131+
builds due to technical limitations.
127132
ghcopts: Flags to pass to Haskell compiler. Subject to Make variable substitution.
128133
This is merged with the ghcopts attribute of rules that depend directly on this haskell_module rule.
129134
plugins: Compiler plugins to use during compilation. (Not implemented, yet)
@@ -140,6 +145,7 @@ def haskell_module(
140145
module_name = module_name,
141146
deps = deps,
142147
cross_library_deps = cross_library_deps,
148+
enable_th = enable_th,
143149
ghcopts = ghcopts,
144150
plugins = plugins,
145151
tools = tools,

haskell/experimental/private/module.bzl

Lines changed: 110 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ load(
1212
"//haskell:private/mode.bzl",
1313
"is_profiling_enabled",
1414
)
15+
load("//haskell:private/pkg_id.bzl", "pkg_id")
1516
load(
1617
"//haskell:private/packages.bzl",
1718
"expose_packages",
@@ -32,6 +33,44 @@ load(
3233
)
3334
load("//haskell:providers.bzl", "HaskellInfo", "HaskellLibraryInfo")
3435

36+
# Note [Narrowed Dependencies]
37+
#
38+
# Usually, when a module M depends on a library L, it doesn't depend on
39+
# all the modules of the library. The user expresses which modules are
40+
# needed with the cross_library_deps attribute and rules_haskell looks
41+
# for the modules in the libraries of the narrowed_deps attribute in the
42+
# enclosing haskell_library.
43+
#
44+
# build_haskell_modules from module.bzl produces dictionaries that say
45+
# for every module label which interface and object files it produces
46+
# and which interface and object files it depends upon transitively.
47+
# These dictionaries end up in the HaskellInfo provider.
48+
#
49+
# It is not strictly necessary to depend on all interface and object
50+
# files transitively, but we have no easy way to discern which files
51+
# from the transitive dependencies are actually needed, so we
52+
# over-approximate by depending on all of them.
53+
#
54+
# When compilation doesn't involve Template Haskell, a module only needs
55+
# the interface files of its module dependencies. When the user
56+
# specifies that a module uses Template Haskell via the enable_th
57+
# attribute, we also pass the potentially needed object files in the
58+
# inputs of the build action of haskell_module.
59+
#
60+
# Telling ghc which interface files are available is easy by specifying
61+
# package databases and package-ids on the command line. Passing object
62+
# files is harder because ghc only expects the object files of libraries
63+
# to be linked together in installed packages. It is possible to tell to
64+
# ghc about individual object files by listing their filepaths on the
65+
# command line, but see Note [Empty Libraries] in haskell_impl.bzl for an
66+
# extra nuance.
67+
#
68+
# Unfortunately, passing object files in the command line doesn't cause
69+
# ghc to load them in the external interpreter, so narrowing doesn't
70+
# work in any configuration needing the external interpreter. Therefore,
71+
# profiling builds use the libraries of narrowed_deps instead of the
72+
# their object files.
73+
3574
def _build_haskell_module(
3675
ctx,
3776
hs,
@@ -46,6 +85,7 @@ def _build_haskell_module(
4685
module_outputs,
4786
interface_inputs,
4887
object_inputs,
88+
narrowed_objects,
4989
module):
5090
"""Build a module
5191
@@ -63,6 +103,7 @@ def _build_haskell_module(
63103
module_outputs: A struct containing the interfaces and object files produced for a haskell_module.
64104
interface_inputs: A depset containing the interface files needed as input
65105
object_inputs: A depset containing the object files needed as input
106+
narrowed_objects: A depset containing the narrowed object files needed as arguments to ghc.
66107
module: The Target of the haskell_module rule
67108
"""
68109

@@ -151,7 +192,12 @@ def _build_haskell_module(
151192
hs,
152193
pkg_info = expose_packages(
153194
package_ids = hs.package_ids,
154-
package_databases = depset(transitive = [dep_info.package_databases, narrowed_deps_info.package_databases]),
195+
package_databases = depset(
196+
transitive = [
197+
dep_info.package_databases,
198+
narrowed_deps_info.empty_lib_package_databases,
199+
],
200+
),
155201
# TODO[AH] Support version macros
156202
version = None,
157203
),
@@ -192,6 +238,7 @@ def _build_haskell_module(
192238
moduleAttr.tools,
193239
]
194240
args.add_all(expand_make_variables("ghcopts", ctx, moduleAttr.ghcopts, module_extra_attrs))
241+
args.add_all(narrowed_objects)
195242

196243
outputs = [module_outputs.hi]
197244
if module_outputs.o:
@@ -210,16 +257,21 @@ def _build_haskell_module(
210257
transitive = [
211258
dep_info.package_databases,
212259
dep_info.interface_dirs,
213-
dep_info.hs_libraries,
214-
narrowed_deps_info.package_databases,
260+
narrowed_deps_info.empty_hs_libraries,
261+
narrowed_deps_info.empty_lib_package_databases,
215262
pkg_info_inputs,
216263
plugin_dep_info.package_databases,
217264
plugin_dep_info.interface_dirs,
218265
plugin_dep_info.hs_libraries,
219266
plugin_tool_inputs,
220267
preprocessors_inputs,
221268
interface_inputs,
222-
object_inputs,
269+
narrowed_objects,
270+
] + [
271+
files
272+
for files in [dep_info.hs_libraries, object_inputs]
273+
# libraries and object inputs are only needed if the module uses TH
274+
if module[HaskellModuleInfo].attr.enable_th
223275
],
224276
),
225277
input_manifests = preprocessors_input_manifests + plugin_tool_input_manifests,
@@ -377,10 +429,15 @@ def _merge_narrowed_deps_dicts(rule_label, narrowed_deps):
377429
narrowed_deps: The contents of the narrowed_deps attribute
378430
379431
Returns:
380-
dict of module labels to their interfaces and the interfaces of their transitive
381-
module dependencies
432+
pair of per_module_transitive_interfaces, per_module_transitive_objects:
433+
per_module_transitive_interfaces: dict of module labels to their
434+
interfaces and the interfaces of their transitive module dependencies
435+
per_module_transitive_objects: dict of module labels to their
436+
object files and the object file of their transitive module
437+
dependencies
382438
"""
383439
per_module_transitive_interfaces = {}
440+
per_module_transitive_objects = {}
384441
for dep in narrowed_deps:
385442
if not HaskellInfo in dep or not HaskellLibraryInfo in dep:
386443
fail("{}: depedency {} is not a haskell_library as required when used in narrowed_deps".format(
@@ -394,7 +451,8 @@ def _merge_narrowed_deps_dicts(rule_label, narrowed_deps):
394451
str(dep.label),
395452
))
396453
_merge_depset_dicts(per_module_transitive_interfaces, lib_info.per_module_transitive_interfaces)
397-
return per_module_transitive_interfaces
454+
_merge_depset_dicts(per_module_transitive_objects, lib_info.per_module_transitive_objects)
455+
return per_module_transitive_interfaces, per_module_transitive_objects
398456

399457
def interfaces_as_list(with_shared, o):
400458
if with_shared:
@@ -411,50 +469,79 @@ def build_haskell_modules(ctx, hs, cc, posix, package_name, with_shared, hidir,
411469
hs: Haskell context
412470
cc: CcInteropInfo, information about C dependencies
413471
posix: posix toolchain
472+
package_name: package name if building a library or empty if building a binary
414473
with_shared: Whether to build dynamic object files
415474
hidir: The directory in which to output interface files
416475
odir: The directory in which to output object files
417476
418477
Returns:
419-
struct(his, dyn_his, os, dyn_os, per_module_transitive_interfaces):
478+
struct(his, dyn_his, os, dyn_os, per_module_transitive_interfaces, per_module_transitive_objects):
420479
his: interface files of all modules in ctx.attr.modules
421480
dyn_his: dynamic interface files of all modules in ctx.attr.modules
422481
os: object files of all modules in ctx.attr.modules
423482
dyn_os: dynamic object files of all modules in ctx.attr.modules
424483
per_module_transitive_interfaces: dict of module labels to their
425484
interfaces and the interfaces of their transitive module
426-
dependencies
485+
dependencies. See Note [Narrowed Dependencies].
486+
per_module_transitive_objects: dict of module labels to their
487+
object files and the object files of their transitive module
488+
dependencies. See Note [Narrowed Dependencies].
427489
"""
490+
per_module_transitive_interfaces, per_module_transitive_objects = _merge_narrowed_deps_dicts(ctx.label, ctx.attr.narrowed_deps)
428491

429-
per_module_transitive_interfaces = _merge_narrowed_deps_dicts(ctx.label, ctx.attr.narrowed_deps)
430-
492+
# We produce separate infos for narrowed_deps and deps because the module
493+
# files in dep_info are given as inputs to the build action, but the
494+
# modules files from narrowed_deps_info are only given when haskell_module
495+
# declares to depend on them.
431496
dep_info = gather_dep_info(ctx.attr.name, ctx.attr.deps)
432497
narrowed_deps_info = gather_dep_info(ctx.attr.name, ctx.attr.narrowed_deps)
498+
all_deps_info = gather_dep_info(ctx.attr.name, ctx.attr.deps + ctx.attr.narrowed_deps)
499+
empty_deps_info = gather_dep_info(ctx.attr.name, [])
433500
transitive_module_deps = _reorder_module_deps_to_postorder(ctx.label, ctx.attr.modules)
434501
module_outputs = {dep.label: _declare_module_outputs(hs, with_shared, hidir, odir, dep) for dep in transitive_module_deps}
435502

436503
module_interfaces = {}
437504
module_objects = {}
438505
for dep in transitive_module_deps:
506+
# called in all cases to validate cross_library_deps, although the output
507+
# might be ignored when disabling narrowing
508+
narrowed_interfaces = _collect_narrowed_deps_module_files(ctx.label, per_module_transitive_interfaces, dep)
509+
enable_th = dep[HaskellModuleInfo].attr.enable_th
510+
511+
# Narrowing doesn't work when using the external interpreter so we disable it here
512+
if enable_th and is_profiling_enabled(hs):
513+
per_module_transitive_interfaces_i = []
514+
per_module_transitive_objects_i = []
515+
dep_info_i = all_deps_info
516+
narrowed_deps_info_i = empty_deps_info
517+
narrowed_interfaces = depset()
518+
narrowed_objects = depset()
519+
else:
520+
dep_info_i = dep_info
521+
narrowed_deps_info_i = narrowed_deps_info
522+
523+
# objects are only needed if the module uses TH
524+
narrowed_objects = _collect_narrowed_deps_module_files(ctx.label, per_module_transitive_objects, dep) if enable_th else depset()
525+
439526
his, os = _collect_module_outputs_of_direct_deps(with_shared, module_outputs, dep)
440527
interface_inputs = _collect_module_inputs(module_interfaces, his, dep)
441528
object_inputs = _collect_module_inputs(module_objects, os, dep)
442-
narrowed_interfaces = _collect_narrowed_deps_module_files(ctx.label, per_module_transitive_interfaces, dep)
443529

444530
_build_haskell_module(
445531
ctx,
446532
hs,
447533
cc,
448534
posix,
449-
dep_info,
450-
narrowed_deps_info,
535+
dep_info_i,
536+
narrowed_deps_info_i,
451537
package_name,
452538
with_shared,
453539
hidir,
454540
odir,
455541
module_outputs[dep.label],
456542
depset(transitive = [interface_inputs, narrowed_interfaces]),
457543
object_inputs,
544+
narrowed_objects,
458545
dep,
459546
)
460547

@@ -477,13 +564,22 @@ def build_haskell_modules(ctx, hs, cc, posix, package_name, with_shared, hidir,
477564
for dep in transitive_module_deps
478565
}
479566
_merge_depset_dicts(per_module_transitive_interfaces0, per_module_transitive_interfaces)
567+
per_module_transitive_objects0 = {
568+
dep.label: depset(
569+
[module_outputs[dep.label].o],
570+
transitive = [module_objects[dep.label]],
571+
)
572+
for dep in transitive_module_deps
573+
}
574+
_merge_depset_dicts(per_module_transitive_objects0, per_module_transitive_objects)
480575

481576
return struct(
482577
his = hi_set,
483578
dyn_his = dyn_hi_set,
484579
os = o_set,
485580
dyn_os = dyn_o_set,
486581
per_module_transitive_interfaces = per_module_transitive_interfaces0,
582+
per_module_transitive_objects = per_module_transitive_objects0,
487583
)
488584

489585
def haskell_module_impl(ctx):

0 commit comments

Comments
 (0)