Module: MilkTea::CBackend::TypeSystem

Included in:
MilkTea::CBackend
Defined in:
lib/milk_tea/core/c_backend/type_system.rb

Constant Summary collapse

PRIMITIVE_C_TYPE_MAP =
{
  "bool" => "bool",
  "byte" => "int8_t",
  "ubyte" => "uint8_t",
  "char" => "char",
  "short" => "int16_t",
  "ushort" => "uint16_t",
  "int" => "int32_t",
  "uint" => "uint32_t",
  "long" => "int64_t",
  "ulong" => "uint64_t",
  "ptr_int" => "intptr_t",
  "ptr_uint" => "uintptr_t",
  "float" => "float",
  "double" => "double",
  "void" => "void",
  "cstr" => "const char*",
}.freeze
SANITIZE_NON_ALNUM_RE =
/[^A-Za-z0-9_]+/
SANITIZE_UNDERSCORES_RE =
/_+/
SANITIZE_LEADING_UNDERSCORES_RE =
/^_+/
SANITIZE_TRAILING_UNDERSCORES_RE =
/_+$/

Instance Method Summary collapse

Instance Method Details

#array_element_type(type) ⇒ Object



325
326
327
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 325

def array_element_type(type)
  type.arguments.first
end

#array_length(type) ⇒ Object



329
330
331
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 329

def array_length(type)
  type.arguments[1].value
end

#array_out_param_declaration(type, name) ⇒ Object



49
50
51
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 49

def array_out_param_declaration(type, name)
  c_declaration(type, "(*#{name})")
end

#array_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


320
321
322
323
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 320

def array_type?(type)
  type.is_a?(Types::GenericInstance) && type.name == "array" && type.arguments.length == 2 &&
    type.arguments[1].is_a?(Types::LiteralTypeArg)
end

#c_backend_pointer_like_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


308
309
310
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 308

def c_backend_pointer_like_type?(type)
  pointer_type?(type) || const_pointer_type?(type) || own_type?(type) || (type.is_a?(Types::Primitive) && type.name == "cstr") || type.is_a?(Types::Function) || type.is_a?(Types::Proc) || type.is_a?(Types::Opaque)
end

#c_declaration(type, name) ⇒ Object



30
31
32
33
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 30

def c_declaration(type, name)
  base, declarator = c_declaration_parts(type, name)
  declarator.empty? ? base : "#{base} #{declarator}"
end

#c_declaration_parts(type, name) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 53

def c_declaration_parts(type, name)
  name = name.to_s

  if array_type?(type)
    declarator = declarator_needs_grouping?(name) ? "(#{name})" : name
    return c_declaration_parts(array_element_type(type), "#{declarator}[#{array_length(type)}]")
  end

  if type.is_a?(Types::Nullable) && type.base.is_a?(Types::Function)
    return c_declaration_parts(type.base, name)
  end

  if type.is_a?(Types::Function)
    params = []
    params << array_out_param_declaration(type.return_type, ARRAY_OUT_PARAM_NAME) if array_type?(type.return_type)
    params.concat(type.params.each_with_index.map do |param, index|
      c_declaration(param.type, param.name || "arg#{index}")
    end)
    params << "..." if type.variadic
    params = ["void"] if params.empty?
    return [c_function_return_type(type.return_type), "(*#{name})(#{params.join(', ')})"]
  end

  if type.is_a?(Types::Proc)
    return [proc_type_name(type), name]
  end

  if mutable_pointer_type?(type) || own_type?(type)
    return c_declaration_parts(type.arguments.first, "*#{name}")
  end

  if const_pointer_type?(type)
    return [generic_c_type(type), name]
  end

  if ref_type?(type)
    ref_arguments = type.arguments
    ref_target = ref_arguments.length == 1 ? ref_arguments.first : ref_arguments[1]
    return c_declaration_parts(ref_target, "*#{name}")
  end

  [c_type(type), name]
end

#c_field_declaration(type, name) ⇒ Object



35
36
37
38
39
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 35

def c_field_declaration(type, name)
  return "uint8_t #{name}" if void_storage_field?(type)

  c_declaration(type, name)
end

#c_function_declaration(return_type, name, params) ⇒ Object



41
42
43
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 41

def c_function_declaration(return_type, name, params)
  c_declaration(array_type?(return_type) ? void_type : return_type, "#{name}(#{params})")
end

#c_function_return_type(type) ⇒ Object



45
46
47
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 45

def c_function_return_type(type)
  c_type(array_type?(type) ? void_type : type)
end

#c_type(type, pointer: false) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 101

def c_type(type, pointer: false)
  case type
  when Types::Nullable
    return c_type(type.base, pointer:) if type.base.is_a?(Types::Function)

    unless c_backend_pointer_like_type?(type.base)
      base = nullable_opt_type_name(type)
      return pointer ? "#{base}*" : base
    end

    base = c_type(type.base)
    base.end_with?("*") ? base : "#{base}*"
  when Types::StringView
    base = "mt_str"
    pointer ? "#{base}*" : base
  when Types::Primitive
    base = primitive_c_type(type.name)
    pointer ? "#{base}*" : base
  when Types::Span
    base = span_type_name(type)
    pointer ? "#{base}*" : base
  when Types::Task
    base = task_type_name(type)
    pointer ? "#{base}*" : base
  when Types::Proc
    base = proc_type_name(type)
    pointer ? "#{base}*" : base
  when Types::Dyn
    base = dyn_type_name(type)
    pointer ? "#{base}*" : base
  when Types::DynVtable
    base = type.linkage_name
    pointer ? "#{base}*" : base
  when Types::Function
    base = c_declaration(type, "")
    pointer ? "#{base}*" : base
  when Types::GenericInstance
    base = generic_c_type(type)
    pointer ? "#{base}*" : base
  when Types::Struct, Types::StructInstance, Types::Union, Types::Enum, Types::Flags, Types::Variant, Types::VariantInstance, Types::VariantArmPayload, Types::Event, Types::Subscription
    base = named_type_c_name(type)
    pointer ? "#{base}*" : base
  when Types::Opaque
    if type.external
      base = external_opaque_c_type(type)
      pointer ? "#{base}*" : base
    else
      base = type.linkage_name || named_type_c_name(type)
      pointer ? "#{base}**" : "#{base}*"
    end
  when Types::Vector
    base = "mt_#{type.name}"
    pointer ? "#{base}*" : base
  when Types::Matrix
    base = "mt_#{type.name}"
    pointer ? "#{base}*" : base
  when Types::Quaternion
    base = "mt_#{type.name}"
    pointer ? "#{base}*" : base
  when Types::SoA
    base = soa_type_name(type)
    pointer ? "#{base}*" : base
  when Types::Simd
    base = simd_type_name(type)
    pointer ? "#{base}*" : base
  when Types::Tuple
    base = tuple_type_name(type)
    pointer ? "#{base}*" : base
  when Types::Handle
    "void*"
  else
    raise CBackendError.new("unsupported C type #{type.class.name}", line: 0, column: 0, path: @path)
  end
end

#const_pointer_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


296
297
298
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 296

def const_pointer_type?(type)
  type.is_a?(Types::GenericInstance) && type.name == "const_ptr" && type.arguments.length == 1
end

#constant_storage(type) ⇒ Object



176
177
178
179
180
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 176

def constant_storage(type)
  return "static const" if array_type?(type)

  c_type(type).start_with?("const ") ? "static" : "static const"
end

#declarator_needs_grouping?(name) ⇒ Boolean

Returns:

  • (Boolean)


97
98
99
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 97

def declarator_needs_grouping?(name)
  !name.empty? && (name.start_with?("*") || name.include?("["))
end

#dyn_type_name(type) ⇒ Object



218
219
220
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 218

def dyn_type_name(type)
  "mt_dyn_#{sanitize_identifier(type.interface_binding.name)}"
end

#external_opaque_c_type(type) ⇒ Object



242
243
244
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 242

def external_opaque_c_type(type)
  type.linkage_name || type.name
end

#generic_c_type(type) ⇒ Object



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 259

def generic_c_type(type)
  case type.name
  when "ptr"
    raise CBackendError.new("ptr requires exactly one type argument", line: 0, column: 0, path: @path) unless type.arguments.length == 1

    "#{c_type(type.arguments.first)}*"
  when "const_ptr"
    raise CBackendError.new("const_ptr requires exactly one type argument", line: 0, column: 0, path: @path) unless type.arguments.length == 1

    "const #{c_type(type.arguments.first)}*"
  when "own"
    raise CBackendError.new("own requires exactly one type argument", line: 0, column: 0, path: @path) unless type.arguments.length == 1

    "#{c_type(type.arguments.first)}*"
  when "ref"
    raise CBackendError.new("ref requires at least one type argument", line: 0, column: 0, path: @path) unless [1, 2].include?(type.arguments.length)

    "#{c_type(type.arguments.length == 1 ? type.arguments.first : type.arguments[1])}*"
  when "array"
    raise CBackendError.new("array requires exactly two type arguments", line: 0, column: 0, path: @path) unless type.arguments.length == 2

    c_type(type.arguments.first)
  when "str_buffer"
    raise CBackendError.new("str_buffer requires exactly one type argument", line: 0, column: 0, path: @path) unless str_buffer_type?(type)

    str_buffer_type_name(type)
  when "atomic"
    "_Atomic #{c_type(type.arguments.first)}"
  else
    raise CBackendError.new("unsupported generic C type #{type.name}", line: 0, column: 0, path: @path)
  end
end

#global_storage(_type) ⇒ Object



182
183
184
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 182

def global_storage(_type)
  "static"
end

#module_c_prefix(module_name) ⇒ Object



251
252
253
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 251

def module_c_prefix(module_name)
  sanitize_identifier(module_name.to_s.tr('.', '_'))
end

#mutable_pointer_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


292
293
294
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 292

def mutable_pointer_type?(type)
  type.is_a?(Types::GenericInstance) && type.name == "ptr" && type.arguments.length == 1
end

#named_type_c_name(type) ⇒ Object



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 226

def named_type_c_name(type)
  return task_type_name(type) if type.is_a?(Types::Task)
  if type.is_a?(Types::VariantArmPayload)
    return "#{named_type_c_name(type.variant_type)}_#{type.arm_name}"
  end

  if type.respond_to?(:linkage_name) && type.linkage_name
    return type.linkage_name
  end

  base_name = type.module_name&.start_with?("std.c.") ? type.name : type.module_name ? "#{module_c_prefix(type.module_name)}_#{type.name}" : type.name
  return base_name unless type.is_a?(Types::StructInstance) || type.is_a?(Types::VariantInstance)

  "#{base_name}_#{sanitize_identifier(type.arguments.join('_'))}"
end

#nullable_opt_type_name(type) ⇒ Object



222
223
224
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 222

def nullable_opt_type_name(type)
  "mt_opt_#{sanitize_identifier(c_type(type.base))}"
end

#own_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


300
301
302
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 300

def own_type?(type)
  type.is_a?(Types::GenericInstance) && type.name == "own" && type.arguments.length == 1
end

#pointer_to(type) ⇒ Object



350
351
352
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 350

def pointer_to(type)
  Types::Registry.generic_instance("ptr", [type])
end

#pointer_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


304
305
306
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 304

def pointer_type?(type)
  mutable_pointer_type?(type)
end

#primitive_c_type(name) ⇒ Object



255
256
257
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 255

def primitive_c_type(name)
  PRIMITIVE_C_TYPE_MAP.fetch(name)
end

#proc_type_name(type) ⇒ Object



214
215
216
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 214

def proc_type_name(type)
  "mt_proc_#{sanitize_identifier(type.to_s)}"
end

#raw_pointer_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


312
313
314
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 312

def raw_pointer_type?(type)
  mutable_pointer_type?(type) || const_pointer_type?(type) || own_type?(type)
end

#ref_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


316
317
318
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 316

def ref_type?(type)
  type.is_a?(Types::GenericInstance) && type.name == "ref" && [1, 2].include?(type.arguments.length)
end

#sanitize_identifier(text) ⇒ Object



246
247
248
249
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 246

def sanitize_identifier(text)
  identifier = text.gsub(SANITIZE_NON_ALNUM_RE, "_").gsub(SANITIZE_UNDERSCORES_RE, "_").sub(SANITIZE_LEADING_UNDERSCORES_RE, "").sub(SANITIZE_TRAILING_UNDERSCORES_RE, "")
  identifier.empty? ? "value" : identifier
end

#simd_type_name(type) ⇒ Object



195
196
197
198
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 195

def simd_type_name(type)
  element_name = sanitize_identifier(type.element_type.to_s)
  "mt_simd_#{element_name}x#{type.lane_count}"
end

#soa_type_name(type) ⇒ Object



190
191
192
193
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 190

def soa_type_name(type)
  element_name = sanitize_identifier(type.element_type.respond_to?(:name) ? type.element_type.name : type.element_type.to_s)
  "mt_soa_#{element_name}_#{type.count}"
end

#span_type_name(type) ⇒ Object



186
187
188
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 186

def span_type_name(type)
  "mt_span_#{sanitize_identifier(type.element_type.to_s)}"
end

#str_buffer_capacity(type) ⇒ Object



338
339
340
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 338

def str_buffer_capacity(type)
  type.arguments.first.value
end

#str_buffer_storage_capacity(type) ⇒ Object



342
343
344
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 342

def str_buffer_storage_capacity(type)
  str_buffer_capacity(type) + 1
end

#str_buffer_type?(type) ⇒ Boolean

Returns:

  • (Boolean)


333
334
335
336
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 333

def str_buffer_type?(type)
  type.is_a?(Types::GenericInstance) && type.name == "str_buffer" && type.arguments.length == 1 &&
    type.arguments.first.is_a?(Types::LiteralTypeArg) && type.arguments.first.value.is_a?(Integer)
end

#str_buffer_type_name(type) ⇒ Object



346
347
348
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 346

def str_buffer_type_name(type)
  "mt_str_buffer_#{str_buffer_capacity(type)}"
end

#task_type_name(type) ⇒ Object



210
211
212
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 210

def task_type_name(type)
  "mt_task_#{sanitize_identifier(type.result_type.to_s)}"
end

#tuple_type_name(type) ⇒ Object



200
201
202
203
204
205
206
207
208
# File 'lib/milk_tea/core/c_backend/type_system.rb', line 200

def tuple_type_name(type)
  sanitized = type.element_types.map { |et| sanitize_identifier(et.to_s) }.join("_")
  base = "mt_tuple_#{sanitized}"
  default_names = type.element_types.each_with_index.map { |_, i| "_#{i}" }
  if type.field_names != default_names
    base << "_" << type.field_names.map { |n| sanitize_identifier(n) }.join("_")
  end
  base
end