What is the low level alternative for bpy.ops.object.material_slot_add()?
Example code 

Adds two material slots, m1  (if there is already an active material slot it will replace it with m1) and  m2

import bpy

mat1 = bpy.data.materials.new("m1")
mat2 = bpy.data.materials.new("m2")

ob = bpy.context.object

ob.active_material = mat1
ob.data.materials.append(mat2)


An empty slot can be added with

ob.data.materials.append(None)


Hence can add either data or object materials this way by setting the slot link.  

import bpy

materials = (
    (bpy.data.materials.new("RED"), 'DATA'),
    (bpy.data.materials.new("BLUE"), 'OBJECT'),
    )

ob = bpy.context.object
me = ob.data

for m, link in materials:

    # add a material adds a slot
    print(m.name)
    me.materials.append(None)

    slot = ob.material_slots[-1]
    slot.link = link
    slot.material = m


Thankyou to @Lambdadelta for pointing out


  
  Note if "Link Materials to" set to "Object" instead of default "Object
  Data" in the blender preferences, then ob.data.materials.append(None)
  adds a slot to all linked objects, whereas
  bpy.ops.object.material_slot_add() adds only to the active object