Seems to be read wrong by my ClangImporter.
My understanding (possibly incorrect) is the behavior is "as expected" if you haven't modified your CTypes.swift. unsigned long gets imported as CUnsignedLong as per BuiltinMappedTypes.def. CTypes.swift has CUnsignedLong = UInt as you mentioned for non-Windows and non-x86_64 platforms at the moment, so it gets translated to UInt. UInt has the same width as Int. It is a bit mysterious as to why it is i16 and not u16 though. 
If you think about the mapping, there are lots of different kinds of types. There are built-ins atoms like int, unsigned int etc, there are built-in composites (like function types, which have a bunch of argument types and one return type), there are "core" typedefs (like uint32_t, this is somewhat fuzzy but you get what I mean), and there are user-defined types and typedefs.
If you think of a type as a tree structure, the built-in atoms are the leaves of the tree. BuiltinMappedTypes.def describes how Clang's built-in atoms translate to Swift (technically, these are just called builtins), barring special hacks cases.
Following the tree structure, translating types between languages is like writing a tree interpreter.
- There is a Clang type -> Swift type tree interpreter in
ImportType.cpp.
- There are Swift type -> Clang type tree interpreters in
GenClangType.cpp/ClangTypeConverter.cpp (these are very similar, so you just look at one without worrying about why there are two of them).
What is confusing me is this, I thought that MappedTypes.def determined the mapping, but this is only used in one place I can see, getSwiftStdlibType in ImportDecl.cpp
MappedTypes.def is essentially a list of special-cases that are handled for better user ergonomics (I might be wrong on the purpose, maybe @jrose can describe it better). For example, typedefs can be imported as the underlying type (i.e. erasure-on-import) or they can be translated to typealiases in Swift (i.e. transliteration-on-import). With these specific types, we want to make sure that no new typealiases are introduced, instead, we hard-code what they get imported to. For example, if a platform header defines typedef int32_t int because it has 32-bit ints. As a base assumption, let's assume that int gets imported as Int (hopefully uncontroversial). Then we do not want the typedef to be erased (e.g. functions that look like they take int32_t arguments look like they take Int arguments in Swift), and we do not want to introduce a new typealias like typealias Int32 = Int. Instead, we want to make sure that int32_t gets mapped to Int32, which is a struct in the stdlib, directly without any extra indirection from a typealias. Since this doesn't fit cleanly into the two most common import strategies, and we care about ergonomics, and this is probably a more scalable solution than modifying headers with attributes for every platform we care about, we hardcode these.