Skip to content

Commit

Permalink
Pass the command line to cl.exe through a file (crystal-lang#9062)
Browse files Browse the repository at this point in the history
This is necessary because large projects (like the Crystal compiler) surpass the maximal size for the command line when linking the numerous object files.

This commit also makes Crystal actually be able to run the linker subprocess, as it removes the unsupported combination of `args` and `shell: true`.
  • Loading branch information
oprypin authored and carlhoerberg committed Apr 29, 2020
1 parent ba8e1bd commit 9fb0cda
Showing 1 changed file with 27 additions and 27 deletions.
54 changes: 27 additions & 27 deletions src/compiler/crystal/compiler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module Crystal
# optionally generates an executable.
class Compiler
CC = ENV["CC"]? || "cc"
CL = "cl"
CL = "cl.exe"

# A source to the compiler: its filename and source code.
record Source,
Expand Down Expand Up @@ -314,22 +314,35 @@ module Crystal

target_machine.emit_obj_to_file llvm_mod, object_name

stdout.puts linker_command(program, object_name, output_filename, nil)
print_command(*linker_command(program, [object_name], output_filename, nil))
end

private def linker_command(program : Program, object_name, output_filename, output_dir)
if program.has_flag? "windows"
if object_name
object_name = %("#{object_name}")
else
object_name = %(%*)
end
private def print_command(command, args)
stdout.puts command.sub(%("${@}"), args && args.join(" "))
end

private def linker_command(program : Program, object_names, output_filename, output_dir)
if program.has_flag? "windows"
if link_flags = @link_flags.presence
link_flags = "/link #{link_flags}"
end

%(#{CL} #{object_name} "/Fe#{output_filename}" #{program.lib_flags} #{link_flags})
args = %(#{object_names.join(" ")} "/Fe#{output_filename}" #{program.lib_flags} #{link_flags})
cmd = "#{CL} #{args}"

if cmd.to_utf16.size > 32000
# The command line would be too big, pass the args through a UTF-16-encoded file instead.
# TODO: Use a proper way to write encoded text to a file when that's supported.
# The first character is the BOM; it will be converted in the same endianness as the rest.
args_16 = "\ufeff#{args}".to_utf16
args_bytes = args_16.to_unsafe.as(UInt8*).to_slice(args_16.bytesize)

args_filename = "#{output_dir}/linker_args.txt"
File.write(args_filename, args_bytes)
cmd = "#{CL} @#{args_filename}"
end

{cmd, nil}
else
if thin_lto
clang = ENV["CLANG"]? || "clang"
Expand All @@ -344,17 +357,11 @@ module Crystal
cc = CC
end

if object_name
object_name = %('#{object_name}')
else
object_name = %("${@}")
end

link_flags = @link_flags || ""
link_flags += " -rdynamic"
link_flags += " -static" if static?

%(#{cc} #{object_name} -o '#{output_filename}' #{link_flags} #{program.lib_flags})
{ %(#{cc} "${@}" -o '#{output_filename}' #{link_flags} #{program.lib_flags}), object_names }
end
end

Expand Down Expand Up @@ -389,9 +396,9 @@ module Crystal

@progress_tracker.stage("Codegen (linking)") do
Dir.cd(output_dir) do
linker_command = linker_command(program, nil, output_filename, output_dir)
linker_command = linker_command(program, object_names, output_filename, output_dir)

process_wrapper(linker_command, object_names) do |command, args|
process_wrapper(*linker_command) do |command, args|
Process.run(command, args, shell: true,
input: Process::Redirect::Close, output: Process::Redirect::Inherit, error: Process::Redirect::Pipe) do |process|
process.error.each_line(chomp: false) do |line|
Expand Down Expand Up @@ -563,15 +570,8 @@ module Crystal
end
end

private def system(command, args = nil)
process_wrapper(command, args) do
::system(command, args)
$?
end
end

private def process_wrapper(command, args = nil)
stdout.puts "#{command} #{args.join " "}" if verbose?
print_command(command, args) if verbose?

status = yield command, args

Expand Down

0 comments on commit 9fb0cda

Please sign in to comment.