Skip to content

Commit b870e83

Browse files
committed
codegen: Use nttp tag values for elided comptime values in templates
Otherwise we'd have to invent a new mangling scheme to encode these. Fixes multiple instantiations of comptime generics with different values.
1 parent 3c8d750 commit b870e83

3 files changed

Lines changed: 260 additions & 1 deletion

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// Expect:
2+
/// - output: "a-one\na-two\nb-one\nb-two\n"
3+
4+
fn combine<comptime a: String, comptime b: String>() throws -> String {
5+
return format("{}-{}", a, b)
6+
}
7+
8+
fn main() {
9+
println("{}", combine<comptime "a", comptime "one">())
10+
println("{}", combine<comptime "a", comptime "two">())
11+
println("{}", combine<comptime "b", comptime "one">())
12+
println("{}", combine<comptime "b", comptime "two">())
13+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// Expect:
2+
/// - output: "hello world\ngoodbye world\n"
3+
4+
fn greet<comptime greeting: String>(anon name: String) throws -> String {
5+
return format("{} {}", greeting, name)
6+
}
7+
8+
fn main() {
9+
println("{}", greet<comptime "hello">("world"))
10+
println("{}", greet<comptime "goodbye">("world"))
11+
}

selfhost/codegen.jakt

Lines changed: 236 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import typechecker {
1212
CheckedVisibility, EnumId, FunctionId, Module, ModuleId, Scope, ScopeId, StructId, Type, TypeId, VarId, builtin
1313
never_type_id, unknown_type_id, void_type_id
1414
}
15-
import types { CheckedGenericParameter, CheckedParameter, FunctionGenericParameter, SafetyMode, StructLikeId }
15+
import types { CheckedGenericParameter, CheckedParameter, FunctionGenericParameter, SafetyMode, StructLikeId, Value }
1616
import utility { Queue, Span, escape_for_quotes, interpret_escapes, join, panic, prepend_to_each, todo }
1717
import compiler { Compiler }
1818
import jakt::path { Path }
@@ -230,6 +230,9 @@ struct CodeGenerator {
230230
yield_method: YieldMethod? = None
231231
// Whether the latest output needs to use ErrorOr as return value.
232232
restricted(codegen_value_match, current_error_handler, codegen_statement) yields_erroror: bool = false
233+
// Map from value type's TypeId to list of unique Const TypeIds of that type; mapping per-type tag enums for non-trivial comptime parameters
234+
comptime_tag_enums: [TypeId: [TypeId]] = [:]
235+
comptime_tags_emitted: bool = false
233236

234237
// `forward_error_with_try` controls whether errors are handled through the TRY() macro or through an external mechanism put by the caller.
235238
// noreturn functions may not throw, so let them crash instead.
@@ -301,6 +304,135 @@ struct CodeGenerator {
301304
panic("Cyclic module imports")
302305
}
303306

307+
fn add_comptime_tag(mut this, const_type_id: TypeId, value: Value) throws {
308+
let value_type_id = value.impl.type_id(&mut .program)
309+
if not .comptime_tag_enums.contains(value_type_id) {
310+
.comptime_tag_enums.set(value_type_id, [const_type_id])
311+
} else {
312+
mut list = .comptime_tag_enums[value_type_id]
313+
for existing in list {
314+
if existing.equals(const_type_id) {
315+
return
316+
}
317+
}
318+
list.push(const_type_id)
319+
.comptime_tag_enums.set(value_type_id, list)
320+
}
321+
}
322+
323+
fn should_use_comptime_tag(this, param_type_id: TypeId) -> bool {
324+
if .program.is_trivial_in_cpp(type_id: param_type_id) {
325+
return false
326+
}
327+
guard .program.get_type(param_type_id) is TypeVariable(is_value, value_type_id) else {
328+
return false
329+
}
330+
return is_value and value_type_id.has_value()
331+
}
332+
333+
fn collect_comptime_tags(mut this) throws {
334+
// Scan all types in all modules for non-trivial comptime args
335+
for module in .program.modules {
336+
for type_with_skip in module.types {
337+
match type_with_skip.type {
338+
GenericInstance(id, args) => {
339+
let struct_ = .program.get_struct(id)
340+
for i in 0..args.size() {
341+
if i < struct_.generic_parameters.size() {
342+
let param_type_id = struct_.generic_parameters[i].type_id
343+
if .should_use_comptime_tag(param_type_id) {
344+
if .program.get_type(args[i]) is Const(value) {
345+
.add_comptime_tag(const_type_id: args[i], value)
346+
}
347+
}
348+
}
349+
}
350+
}
351+
GenericEnumInstance(id, args) => {
352+
let enum_ = .program.get_enum(id)
353+
for i in 0..args.size() {
354+
if i < enum_.generic_parameters.size() {
355+
let param_type_id = enum_.generic_parameters[i].type_id
356+
if .should_use_comptime_tag(param_type_id) {
357+
if .program.get_type(args[i]) is Const(value) {
358+
.add_comptime_tag(const_type_id: args[i], value)
359+
}
360+
}
361+
}
362+
}
363+
}
364+
else => {}
365+
}
366+
}
367+
368+
for function in module.functions {
369+
for specialization in function.generics.specializations {
370+
for i in 0..specialization.size() {
371+
if i < function.generics.params.size() {
372+
let param_type_id = function.generics.params[i].checked_parameter.type_id
373+
if .should_use_comptime_tag(param_type_id) {
374+
if .program.get_type(specialization[i]) is Const(value) {
375+
.add_comptime_tag(const_type_id: specialization[i], value)
376+
}
377+
}
378+
}
379+
}
380+
}
381+
}
382+
}
383+
}
384+
385+
fn sanitize_cpp_identifier(anon name: String) -> String {
386+
mut result = StringBuilder::create()
387+
for cp in name.code_points() {
388+
if (cp >= 'a' and cp <= 'z') or (cp >= 'A' and cp <= 'Z') or (cp >= '0' and cp <= '9') or cp == '_' {
389+
result.append(cp)
390+
} else {
391+
result.append('_')
392+
}
393+
}
394+
return result.to_string()
395+
}
396+
397+
fn codegen_comptime_tag_enums(mut this, output: &mut StringBuilder) throws {
398+
for entry in .comptime_tag_enums {
399+
let value_type_id = entry.0
400+
let const_type_ids = entry.1
401+
let type_name = .program.type_name(value_type_id)
402+
let sanitized_name = CodeGenerator::sanitize_cpp_identifier(type_name)
403+
404+
output.appendff("enum class __jakt_comptime_{} : size_t {{\n", sanitized_name)
405+
for i in 0..const_type_ids.size() {
406+
// Use codegen_type to generate the C++ representation as a comment
407+
let value_repr = .codegen_type(const_type_ids[i])
408+
output.appendff("__value_{}, // {}\n", i, value_repr)
409+
}
410+
output.append("};\n")
411+
}
412+
}
413+
414+
fn get_comptime_tag_for_const(mut this, type_id: TypeId) throws -> (String, String)? {
415+
guard .program.get_type(type_id) is Const(value) else {
416+
return None
417+
}
418+
419+
let value_type_id = value.impl.type_id(program: &mut .program)
420+
guard .comptime_tag_enums.contains(value_type_id) else {
421+
return None
422+
}
423+
424+
let const_type_ids = .comptime_tag_enums[value_type_id]
425+
for i in 0..const_type_ids.size() {
426+
if const_type_ids[i].equals(type_id) {
427+
let type_name = .program.type_name(value_type_id)
428+
let sanitized_name = CodeGenerator::sanitize_cpp_identifier(type_name)
429+
return (format("__jakt_comptime_{}", sanitized_name), format("__value_{}", i))
430+
}
431+
}
432+
433+
return None
434+
}
435+
304436
fn generate(compiler: Compiler, anon program: CheckedProgram, debug_info: bool, exported_files: &mut [String:String]) throws -> [String:(String, String)] {
305437
mut generator = CodeGenerator(
306438
compiler
@@ -320,6 +452,8 @@ struct CodeGenerator {
320452
fresh_label_counter: 0
321453
)
322454

455+
generator.collect_comptime_tags()
456+
323457
mut result: [String:(String, String)] = [:]
324458

325459
// Share the output builder between stages so that any module
@@ -405,6 +539,12 @@ struct CodeGenerator {
405539
mut after_headers = StringBuilder::create()
406540
after_headers.append("namespace Jakt {\n")
407541

542+
// Emit comptime tag enums once in the first module header
543+
if module.is_root and not generator.comptime_tags_emitted {
544+
generator.codegen_comptime_tag_enums(output: &mut after_headers)
545+
generator.comptime_tags_emitted = true
546+
}
547+
408548
if not module.is_root {
409549
generator.namespace_stack.push(module.name)
410550
}
@@ -917,6 +1057,21 @@ struct CodeGenerator {
9171057
if i < function.generics.params.size() {
9181058
let param_type_id = function.generics.params[i].checked_parameter.type_id
9191059
if not .program.is_trivial_in_cpp(type_id: param_type_id) {
1060+
if .program.get_type(param_type_id) is TypeVariable(is_value, value_type_id) and is_value and value_type_id.has_value() {
1061+
if .comptime_tag_enums.contains(value_type_id!) {
1062+
if .program.get_type(arg) is TypeVariable(name) {
1063+
if first {
1064+
first = false
1065+
} else {
1066+
output.append(", ")
1067+
}
1068+
let type_name = .program.type_name(value_type_id!)
1069+
let sanitized_name = CodeGenerator::sanitize_cpp_identifier(type_name)
1070+
output.appendff("__jakt_comptime_{} {}", sanitized_name, name)
1071+
}
1072+
continue
1073+
}
1074+
}
9201075
continue
9211076
}
9221077
}
@@ -980,6 +1135,14 @@ struct CodeGenerator {
9801135
if i < function.generics.params.size() {
9811136
let param_type_id = function.generics.params[i].checked_parameter.type_id
9821137
if not .program.is_trivial_in_cpp(type_id: param_type_id) {
1138+
if .get_comptime_tag_for_const(type_id: specialization2[i]) is Some(tag) {
1139+
if first {
1140+
first = false
1141+
} else {
1142+
output.append(", ")
1143+
}
1144+
output.appendff("{}::{}", tag.0, tag.1)
1145+
}
9831146
continue
9841147
}
9851148
}
@@ -1483,6 +1646,11 @@ struct CodeGenerator {
14831646
if .program.is_trivial_in_cpp(type_id: param.type_id) {
14841647
return true
14851648
}
1649+
if .program.get_type(param.type_id) is TypeVariable(is_value, value_type_id) and is_value and value_type_id.has_value() {
1650+
if .comptime_tag_enums.contains(value_type_id!) {
1651+
return true
1652+
}
1653+
}
14861654
}
14871655
return false
14881656
}
@@ -1492,6 +1660,11 @@ struct CodeGenerator {
14921660
if .program.is_trivial_in_cpp(type_id: param.checked_parameter.type_id) {
14931661
return true
14941662
}
1663+
if .program.get_type(param.checked_parameter.type_id) is TypeVariable(is_value, value_type_id) and is_value and value_type_id.has_value() {
1664+
if .comptime_tag_enums.contains(value_type_id!) {
1665+
return true
1666+
}
1667+
}
14951668
}
14961669
return false
14971670
}
@@ -1549,6 +1722,25 @@ struct CodeGenerator {
15491722
mut first = true
15501723
for id in parameters {
15511724
if not .program.is_trivial_in_cpp(type_id: id) {
1725+
// Check if this is a non-trivial comptime value param with a tag enum
1726+
if .program.get_type(id) is TypeVariable(is_value, value_type_id) and is_value and value_type_id.has_value() {
1727+
if .comptime_tag_enums.contains(value_type_id!) {
1728+
if first {
1729+
first = false
1730+
} else {
1731+
output.append(",")
1732+
}
1733+
let type_name = .program.type_name(value_type_id!)
1734+
let sanitized_name = CodeGenerator::sanitize_cpp_identifier(type_name)
1735+
let tag_enum_name = format("__jakt_comptime_{}", sanitized_name)
1736+
output.append(tag_enum_name)
1737+
output.append(" ")
1738+
let name = .codegen_type(id)
1739+
output.append(name)
1740+
names.push(name)
1741+
continue
1742+
}
1743+
}
15521744
continue
15531745
}
15541746
if first {
@@ -4772,6 +4964,14 @@ struct CodeGenerator {
47724964
if i < struct_.generic_parameters.size() {
47734965
let param_type_id = struct_.generic_parameters[i].type_id
47744966
if not .program.is_trivial_in_cpp(type_id: param_type_id) {
4967+
if .get_comptime_tag_for_const(type_id: args[i]) is Some(tag) {
4968+
if not first {
4969+
output.append(", ")
4970+
} else {
4971+
first = false
4972+
}
4973+
output.appendff("{}::{}", tag.0, tag.1)
4974+
}
47754975
continue
47764976
}
47774977
}
@@ -4792,6 +4992,14 @@ struct CodeGenerator {
47924992
if i < struct_.generic_parameters.size() {
47934993
let param_type_id = struct_.generic_parameters[i].type_id
47944994
if not .program.is_trivial_in_cpp(type_id: param_type_id) {
4995+
if .get_comptime_tag_for_const(type_id: args[i]) is Some(tag) {
4996+
if not first {
4997+
output.append(", ")
4998+
} else {
4999+
first = false
5000+
}
5001+
output.appendff("{}::{}", tag.0, tag.1)
5002+
}
47955003
continue
47965004
}
47975005
}
@@ -4851,6 +5059,9 @@ struct CodeGenerator {
48515059
if i < callee.generics.params.size() {
48525060
let param_type_id = callee.generics.params[i].checked_parameter.type_id
48535061
if not .program.is_trivial_in_cpp(type_id: param_type_id) {
5062+
if .get_comptime_tag_for_const(type_id: generic_parameters[i]) is Some(tag) {
5063+
types.push(format("{}::{}", tag.0, tag.1))
5064+
}
48545065
continue
48555066
}
48565067
}
@@ -5319,6 +5530,14 @@ struct CodeGenerator {
53195530
if i < struct_.generic_parameters.size() {
53205531
let param_type_id = struct_.generic_parameters[i].type_id
53215532
if not .program.is_trivial_in_cpp(type_id: param_type_id) {
5533+
if .get_comptime_tag_for_const(type_id: args[i]) is Some(tag) {
5534+
if not first {
5535+
output += ","
5536+
} else {
5537+
first = false
5538+
}
5539+
output += format("{}::{}", tag.0, tag.1)
5540+
}
53225541
continue
53235542
}
53245543
}
@@ -5374,6 +5593,14 @@ struct CodeGenerator {
53745593
if i < enum_.generic_parameters.size() {
53755594
let param_type_id = enum_.generic_parameters[i].type_id
53765595
if not .program.is_trivial_in_cpp(type_id: param_type_id) {
5596+
if .get_comptime_tag_for_const(type_id: args[i]) is Some(tag) {
5597+
if not first {
5598+
output.append(", ")
5599+
} else {
5600+
first = false
5601+
}
5602+
output.appendff("{}::{}", tag.0, tag.1)
5603+
}
53775604
continue
53785605
}
53795606
}
@@ -5915,6 +6142,14 @@ struct CodeGenerator {
59156142
if i < function.generics.params.size() {
59166143
let param_type_id = function.generics.params[i].checked_parameter.type_id
59176144
if not .program.is_trivial_in_cpp(type_id: param_type_id) {
6145+
if .get_comptime_tag_for_const(type_id: instantiation[i]) is Some(tag) {
6146+
if not first {
6147+
output.append(", ")
6148+
} else {
6149+
first = false
6150+
}
6151+
output.appendff("{}::{}", tag.0, tag.1)
6152+
}
59186153
continue
59196154
}
59206155
}

0 commit comments

Comments
 (0)