Expression Compiler

The following program takes a mathematical expression as a command line argument. It then evaluates the expression for some specific points to generate example cases.
Then Evoasm is used to find a sequence of machine code instructions whose input and output matches the example set, that is one that calculates the expression.

The time it takes to find such a sequence of machine code varies greatly, and sometimes Evoasm even fails to find one at all. It also strongly depends on the chosen meta-parameters (kernel size, deme size etc.). Keep that in mind if you cannot exactly reproduce what is shown below.

At the moment, expressions are restricted to a single variable x, however, adding support for multiple variables would not be too hard to implement.

Given below are some example runs with results (introns have been eliminated). The code is shown at the very bottom of the page.

The command used was

$ bundle exec ruby docs/examples/expr_comp.rb --use-gem-libevoasm "<expr>"

The line Input registers: <reg>:<index> gives information about which registers have been initialized with which argument; x being the 0th argument. The remaining arguments are possible constants, appearing in the expression.

Example 1 – x/2.0

0x7f023ca0100f: vdivpd  xmm2, xmm2, xmm3
Input registers: xmm2:0, xmm3:1
Output registers: xmm2
Average loss is 0.0
Generations: 0

Example 2 – sqrt(x)

0x7fd1928fc00f: vfmadd231ps xmm1, xmm0, xmm1
0x7fd1928fc014: vshufpd xmm3, xmm0, xmm1, 0x4e
0x7fd1928fc019: vphaddsw    ymm2, ymm3, ymm3
0x7fd1928fc01e: vsqrtpd xmm2, xmm0
Input registers: xmm0:0, xmm1:0, xmm3:0
Output registers: xmm2
Average loss is 0.0
Generations: 0

Example 3 – sqrt(x**3)

0x7fb54e1ca00f: sqrtsd  xmm1, xmm0
0x7fb54e1ca013: movq    xmm3, xmm1
0x7fb54e1ca017: vfnmadd213pd    xmm1, xmm2, xmm3
0x7fb54e1ca01c: addsubpd    xmm3, xmm1
Input registers: xmm0:0, xmm1:1, xmm2:0
Output registers: xmm3
Average loss is 0.0
Generations: 1620

Example 4 – 2*x

0x7f400818100f: vpsadbw ymm2, ymm1, ymm3
0x7f4008181013: vmpsadbw    xmm3, xmm1, xmm0, 2
0x7f4008181019: vcvtsi2sd   xmm2, xmm3, eax
0x7f400818101d: vpsrad  ymm3, ymm3, xmm2
0x7f4008181021: addpd   xmm2, xmm3
0x7f4008181025: addsd   xmm1, xmm1
0x7f4008181029: cvttpd2dq   xmm2, xmm1
Input registers: a:0, xmm0:1, xmm1:0, xmm3:1
Output registers: xmm2
Average loss is 0.0
Generations: 42

Example 5 – 3*x

0x7fcd9b73500f: vpsrad  ymm1, ymm1, xmm1
0x7fcd9b735013: vmulsd  xmm1, xmm1, xmm1
0x7fcd9b735017: vfmaddsub213pd  ymm0, ymm3, ymm1
0x7fcd9b73501c: vcvttpd2dq  xmm3, ymm0
0x7fcd9b735020: orpd    xmm3, xmm3
Input registers: xmm0:0, xmm1:1, xmm3:1
Output registers: xmm3
Average loss is 0.0
Generations: 90

Example 6 – (3.5*x) + 105.01

0x7f9ef260a00f: vfmsubadd231pd  ymm2, ymm1, ymm0
Input registers: xmm0:0, xmm1:1, xmm2:2
Output registers: xmm2
Average loss is 0.0
Generations: 264

Code

require 'evoasm'
require 'evoasm/x64'

require 'colorize'

Evoasm.log_level = :info

expression = ARGV[0]

module ExpressionScope
  extend Math
end

p expression

imms = expression.scan(/\b\d+(?:\.\d+)?\b/).map(&:to_f)
examples = (0..10).map do |x|
  [[x.to_f, *imms], ExpressionScope.module_eval(expression)]
end.to_h


instructions = Evoasm::X64.instruction_names(:xmm)

instructions += %i(
  mov_r8_imm8
  mov_r16_imm16
  mov_r32_imm32
  mov_rm8_imm8
  mov_rm16_imm16
  mov_rm32_imm32
  mov_rm64_imm32
)

parameters = Evoasm::Population::Parameters.new do |p|
  p.instructions = instructions
  p.examples = examples
  p.deme_size = 256
  p.deme_count = 6
  p.kernel_size = 10
  p.distance_metric = :absdiff
  p.parameters = %i(reg0 reg1 reg2 reg3 imm0)

  regs = %i(xmm0 xmm1 xmm2 xmm3 a b c d)


  domains = {
    reg0: regs,
    reg1: regs,
    reg2: regs,
    reg3: regs
  }

  imm_domain = imms.flat_map do |imm|
    [
      [imm.to_f].pack('d').unpack('Q'),
      [imm.to_f].pack('f').unpack('L'),
      imm
    ]
  end

  domains[:imm0] = imm_domain unless imm_domain.empty?


  p.domains = domains
end

puts "Supported features:"
Evoasm::X64.features.each do |feature, supported|
  puts "\t#{feature.to_s.upcase}: #{supported ? 'YES' : 'NO'}"
end
puts

population = Evoasm::Population.new parameters
kernel, loss = population.run loss: 0.0, max_generations: 100000 do
  population.report
end

puts kernel.disassemble format: true

puts

kernel = kernel.eliminate_introns
puts kernel.disassemble format: true

puts "Input registers: #{kernel.input_mapping.map { |reg, arg| "#{reg.to_s.bold}:#{arg}"}.join(', ')}"
puts "Output registers: #{kernel.output_registers.join(', ').bold}"
puts "Average loss is #{loss.to_s.bold}"
puts "Generations: #{population.generation}"
puts
puts "x\texpected\tactual"
examples.each do |x, y|
  puts "#{x}\t#{y.round(3)}\t\t#{kernel.run(*x).round(3)}"
end