Browse Source

Move all os specific stuff for the runtime to one file

gingerBill 5 years ago
parent
commit
9d91c46cb4
5 changed files with 272 additions and 151 deletions
  1. 15 0
      core/runtime/core.odin
  2. 0 21
      core/runtime/defaults.odin
  3. 28 31
      core/runtime/error_checks.odin
  4. 45 0
      core/runtime/os_specific.odin
  5. 184 99
      core/runtime/print.odin

+ 15 - 0
core/runtime/core.odin

@@ -522,6 +522,21 @@ init_global_temporary_allocator :: proc(data: []byte, backup_allocator := contex
 }
 
 
+default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) {
+	fd := os_stderr();
+	print_caller_location(fd, loc);
+	print_string(fd, " ");
+	print_string(fd, prefix);
+	if len(message) > 0 {
+		print_string(fd, ": ");
+		print_string(fd, message);
+	}
+	print_byte(fd, '\n');
+	debug_trap();
+}
+
+
+
 
 @builtin
 copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int {

+ 0 - 21
core/runtime/defaults.odin

@@ -1,21 +0,0 @@
-package runtime
-
-import "core:os"
-
-current_thread_id :: proc "contextless" () -> int {
-	return os.current_thread_id();
-}
-
-default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code_Location) {
-	fd := os.stderr;
-	print_caller_location(fd, loc);
-	os.write_string(fd, " ");
-	os.write_string(fd, prefix);
-	if len(message) > 0 {
-		os.write_string(fd, ": ");
-		os.write_string(fd, message);
-	}
-	os.write_byte(fd, '\n');
-	debug_trap();
-}
-

+ 28 - 31
core/runtime/error_checks.odin

@@ -1,8 +1,5 @@
 package runtime
 
-import "core:os"
-
-
 bounds_trap :: proc "contextless" () -> ! {
 	when ODIN_OS == "windows" {
 		windows_trap_array_bounds();
@@ -24,13 +21,13 @@ bounds_check_error :: proc "contextless" (file: string, line, column: int, index
 	if 0 <= index && index < count do return;
 	handle_error :: proc "contextless" (file: string, line, column: int, index, count: int) {
 		context = default_context();
-		fd := os.stderr;
+		fd := os_stderr();
 		print_caller_location(fd, Source_Code_Location{file, line, column, "", 0});
-		os.write_string(fd, " Index ");
+		print_string(fd, " Index ");
 		print_i64(fd, i64(index));
-		os.write_string(fd, " is out of bounds range 0:");
+		print_string(fd, " is out of bounds range 0:");
 		print_i64(fd, i64(count));
-		os.write_byte(fd, '\n');
+		print_byte(fd, '\n');
 		bounds_trap();
 	}
 	handle_error(file, line, column, index, count);
@@ -38,15 +35,15 @@ bounds_check_error :: proc "contextless" (file: string, line, column: int, index
 
 slice_handle_error :: proc "contextless" (file: string, line, column: int, lo, hi: int, len: int) {
 	context = default_context();
-	fd := os.stderr;
+	fd := os_stderr();
 	print_caller_location(fd, Source_Code_Location{file, line, column, "", 0});
-	os.write_string(fd, " Invalid slice indices: ");
+	print_string(fd, " Invalid slice indices: ");
 	print_i64(fd, i64(lo));
-	os.write_string(fd, ":");
+	print_string(fd, ":");
 	print_i64(fd, i64(hi));
-	os.write_string(fd, ":");
+	print_string(fd, ":");
 	print_i64(fd, i64(len));
-	os.write_byte(fd, '\n');
+	print_byte(fd, '\n');
 	bounds_trap();
 }
 
@@ -64,15 +61,15 @@ dynamic_array_expr_error :: proc "contextless" (file: string, line, column: int,
 	if 0 <= low && low <= high && high <= max do return;
 	handle_error :: proc "contextless" (file: string, line, column: int, low, high, max: int) {
 		context = default_context();
-		fd := os.stderr;
+		fd := os_stderr();
 		print_caller_location(fd, Source_Code_Location{file, line, column, "", 0});
-		os.write_string(fd, " Invalid dynamic array values: ");
+		print_string(fd, " Invalid dynamic array values: ");
 		print_i64(fd, i64(low));
-		os.write_string(fd, ":");
+		print_string(fd, ":");
 		print_i64(fd, i64(high));
-		os.write_string(fd, ":");
+		print_string(fd, ":");
 		print_i64(fd, i64(max));
-		os.write_byte(fd, '\n');
+		print_byte(fd, '\n');
 		bounds_trap();
 	}
 	handle_error(file, line, column, low, high, max);
@@ -83,13 +80,13 @@ type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column
 	if ok do return;
 	handle_error :: proc "contextless" (file: string, line, column: int, from, to: typeid) {
 		context = default_context();
-		fd := os.stderr;
+		fd := os_stderr();
 		print_caller_location(fd, Source_Code_Location{file, line, column, "", 0});
-		os.write_string(fd, " Invalid type assertion from ");
+		print_string(fd, " Invalid type assertion from ");
 		print_typeid(fd, from);
-		os.write_string(fd, " to ");
+		print_string(fd, " to ");
 		print_typeid(fd, to);
-		os.write_byte(fd, '\n');
+		print_byte(fd, '\n');
 		type_assertion_trap();
 	}
 	handle_error(file, line, column, from, to);
@@ -99,11 +96,11 @@ make_slice_error_loc :: inline proc "contextless" (loc := #caller_location, len:
 	if 0 <= len do return;
 	handle_error :: proc "contextless" (loc: Source_Code_Location, len: int) {
 		context = default_context();
-		fd := os.stderr;
+		fd := os_stderr();
 		print_caller_location(fd, loc);
-		os.write_string(fd, " Invalid slice length for make: ");
+		print_string(fd, " Invalid slice length for make: ");
 		print_i64(fd, i64(len));
-		os.write_byte(fd, '\n');
+		print_byte(fd, '\n');
 		bounds_trap();
 	}
 	handle_error(loc, len);
@@ -113,13 +110,13 @@ make_dynamic_array_error_loc :: inline proc "contextless" (using loc := #caller_
 	if 0 <= len && len <= cap do return;
 	handle_error :: proc "contextless" (loc: Source_Code_Location, len, cap: int) {
 		context = default_context();
-		fd := os.stderr;
+		fd := os_stderr();
 		print_caller_location(fd, loc);
-		os.write_string(fd, " Invalid dynamic array parameters for make: ");
+		print_string(fd, " Invalid dynamic array parameters for make: ");
 		print_i64(fd, i64(len));
-		os.write_byte(fd, ':');
+		print_byte(fd, ':');
 		print_i64(fd, i64(cap));
-		os.write_byte(fd, '\n');
+		print_byte(fd, '\n');
 		bounds_trap();
 	}
 	handle_error(loc, len, cap);
@@ -129,11 +126,11 @@ make_map_expr_error_loc :: inline proc "contextless" (loc := #caller_location, c
 	if 0 <= cap do return;
 	handle_error :: proc "contextless" (loc: Source_Code_Location, cap: int) {
 		context = default_context();
-		fd := os.stderr;
+		fd := os_stderr();
 		print_caller_location(fd, loc);
-		os.write_string(fd, " Invalid map capacity for make: ");
+		print_string(fd, " Invalid map capacity for make: ");
 		print_i64(fd, i64(cap));
-		os.write_byte(fd, '\n');
+		print_byte(fd, '\n');
 		bounds_trap();
 	}
 	handle_error(loc, cap);

+ 45 - 0
core/runtime/os_specific.odin

@@ -0,0 +1,45 @@
+package runtime
+
+when ODIN_OS == "freestanding" {
+	_OS_Errno  :: distinct int;
+	_OS_Handle :: distinct uintptr;
+
+	os_stdout :: proc "contextless" () -> _OS_Handle {
+		return 1;
+	}
+	os_stderr :: proc "contextless" () -> _OS_Handle {
+		return 2;
+	}
+
+	// TODO(bill): reimplement `os.write`
+	os_write :: proc(fd: _OS_Handle, data: []byte) -> (int, _OS_Errno) {
+		return 0, -1;
+	}
+
+	current_thread_id :: proc "contextless" () -> int {
+		return 0;
+	}
+
+} else {
+	import "core:os"
+
+	_OS_Errno  :: distinct int;
+	_OS_Handle :: os.Handle;
+
+	os_stdout :: proc "contextless" () -> _OS_Handle {
+		return os.stdout;
+	}
+	os_stderr :: proc "contextless" () -> _OS_Handle {
+		return os.stderr;
+	}
+
+	// TODO(bill): reimplement `os.write`
+	os_write :: proc(fd: _OS_Handle, data: []byte) -> (int, _OS_Errno) {
+		n, err := os.write(fd, data);
+		return int(n), _OS_Errno(err);
+	}
+
+	current_thread_id :: proc "contextless" () -> int {
+		return os.current_thread_id();
+	}
+}

+ 184 - 99
core/runtime/print.odin

@@ -1,9 +1,93 @@
 package runtime
 
-import "core:os"
+_INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz";
 
-print_u64 :: proc(fd: os.Handle, x: u64) {
-	digits := "0123456789";
+encode_rune :: proc(c: rune) -> ([4]u8, int) {
+	r := c;
+
+	buf: [4]u8;
+	i := u32(r);
+	mask :: u8(0x3f);
+	if i <= 1<<7-1 {
+		buf[0] = u8(r);
+		return buf, 1;
+	}
+	if i <= 1<<11-1 {
+		buf[0] = 0xc0 | u8(r>>6);
+		buf[1] = 0x80 | u8(r) & mask;
+		return buf, 2;
+	}
+
+	// Invalid or Surrogate range
+	if i > 0x0010ffff ||
+	   (0xd800 <= i && i <= 0xdfff) {
+		r = 0xfffd;
+	}
+
+	if i <= 1<<16-1 {
+		buf[0] = 0xe0 | u8(r>>12);
+		buf[1] = 0x80 | u8(r>>6) & mask;
+		buf[2] = 0x80 | u8(r)    & mask;
+		return buf, 3;
+	}
+
+	buf[0] = 0xf0 | u8(r>>18);
+	buf[1] = 0x80 | u8(r>>12) & mask;
+	buf[2] = 0x80 | u8(r>>6)  & mask;
+	buf[3] = 0x80 | u8(r)     & mask;
+	return buf, 4;
+}
+
+print_string :: proc(fd: _OS_Handle, str: string) -> (int, _OS_Errno) {
+	return os_write(fd, transmute([]byte)str);
+}
+
+print_byte :: proc(fd: _OS_Handle, b: byte) -> (int, _OS_Errno) {
+	return os_write(fd, []byte{b});
+}
+
+print_encoded_rune :: proc(fd: _OS_Handle, r: rune) {
+	print_byte(fd, '\'');
+
+	switch r {
+	case '\a': print_string(fd, "\\a");
+	case '\b': print_string(fd, "\\b");
+	case '\e': print_string(fd, "\\e");
+	case '\f': print_string(fd, "\\f");
+	case '\n': print_string(fd, "\\n");
+	case '\r': print_string(fd, "\\r");
+	case '\t': print_string(fd, "\\t");
+	case '\v': print_string(fd, "\\v");
+	case:
+		if r <= 0 {
+			print_string(fd, "\\x00");
+		} else if r < 32 {
+			digits := _INTEGER_DIGITS;
+			n0, n1 := u8(r) >> 4, u8(r) & 0xf;
+			print_string(fd, "\\x");
+			print_byte(fd, digits[n0]);
+			print_byte(fd, digits[n1]);
+		} else {
+			print_rune(fd, r);
+		}
+	}
+	print_byte(fd, '\'');
+}
+
+print_rune :: proc(fd: _OS_Handle, r: rune) -> (int, _OS_Errno) {
+	RUNE_SELF :: 0x80;
+
+	if r < RUNE_SELF {
+		return print_byte(fd, byte(r));
+	}
+
+	b, n := encode_rune(r);
+	return os_write(fd, b[:n]);
+}
+
+
+print_u64 :: proc(fd: _OS_Handle, x: u64) {
+	digits := _INTEGER_DIGITS;
 
 	a: [129]byte;
 	i := len(a);
@@ -15,11 +99,12 @@ print_u64 :: proc(fd: os.Handle, x: u64) {
 	}
 	i -= 1; a[i] = digits[u % b];
 
-	os.write(fd, a[i:]);
+	os_write(fd, a[i:]);
 }
 
-print_i64 :: proc(fd: os.Handle, x: i64) {
-	digits := "0123456789";
+
+print_i64 :: proc(fd: _OS_Handle, x: i64) {
+	digits := _INTEGER_DIGITS;
 	b :: i64(10);
 
 	u := x;
@@ -37,257 +122,257 @@ print_i64 :: proc(fd: os.Handle, x: i64) {
 		i -= 1; a[i] = '-';
 	}
 
-	os.write(fd, a[i:]);
+	os_write(fd, a[i:]);
 }
 
-print_caller_location :: proc(fd: os.Handle, using loc: Source_Code_Location) {
-	os.write_string(fd, file_path);
-	os.write_byte(fd, '(');
+print_caller_location :: proc(fd: _OS_Handle, using loc: Source_Code_Location) {
+	print_string(fd, file_path);
+	print_byte(fd, '(');
 	print_u64(fd, u64(line));
-	os.write_byte(fd, ':');
+	print_byte(fd, ':');
 	print_u64(fd, u64(column));
-	os.write_byte(fd, ')');
+	print_byte(fd, ')');
 }
-print_typeid :: proc(fd: os.Handle, id: typeid) {
+print_typeid :: proc(fd: _OS_Handle, id: typeid) {
 	if id == nil {
-		os.write_string(fd, "nil");
+		print_string(fd, "nil");
 	} else {
 		ti := type_info_of(id);
 		print_type(fd, ti);
 	}
 }
-print_type :: proc(fd: os.Handle, ti: ^Type_Info) {
+print_type :: proc(fd: _OS_Handle, ti: ^Type_Info) {
 	if ti == nil {
-		os.write_string(fd, "nil");
+		print_string(fd, "nil");
 		return;
 	}
 
 	switch info in ti.variant {
 	case Type_Info_Named:
-		os.write_string(fd, info.name);
+		print_string(fd, info.name);
 	case Type_Info_Integer:
 		switch ti.id {
-		case int:     os.write_string(fd, "int");
-		case uint:    os.write_string(fd, "uint");
-		case uintptr: os.write_string(fd, "uintptr");
+		case int:     print_string(fd, "int");
+		case uint:    print_string(fd, "uint");
+		case uintptr: print_string(fd, "uintptr");
 		case:
-			os.write_byte(fd, 'i' if info.signed else 'u');
+			print_byte(fd, 'i' if info.signed else 'u');
 			print_u64(fd, u64(8*ti.size));
 		}
 	case Type_Info_Rune:
-		os.write_string(fd, "rune");
+		print_string(fd, "rune");
 	case Type_Info_Float:
-		os.write_byte(fd, 'f');
+		print_byte(fd, 'f');
 		print_u64(fd, u64(8*ti.size));
 	case Type_Info_Complex:
-		os.write_string(fd, "complex");
+		print_string(fd, "complex");
 		print_u64(fd, u64(8*ti.size));
 	case Type_Info_Quaternion:
-		os.write_string(fd, "quaternion");
+		print_string(fd, "quaternion");
 		print_u64(fd, u64(8*ti.size));
 	case Type_Info_String:
-		os.write_string(fd, "string");
+		print_string(fd, "string");
 	case Type_Info_Boolean:
 		switch ti.id {
-		case bool: os.write_string(fd, "bool");
+		case bool: print_string(fd, "bool");
 		case:
-			os.write_byte(fd, 'b');
+			print_byte(fd, 'b');
 			print_u64(fd, u64(8*ti.size));
 		}
 	case Type_Info_Any:
-		os.write_string(fd, "any");
+		print_string(fd, "any");
 	case Type_Info_Type_Id:
-		os.write_string(fd, "typeid");
+		print_string(fd, "typeid");
 
 	case Type_Info_Pointer:
 		if info.elem == nil {
-			os.write_string(fd, "rawptr");
+			print_string(fd, "rawptr");
 		} else {
-			os.write_string(fd, "^");
+			print_string(fd, "^");
 			print_type(fd, info.elem);
 		}
 	case Type_Info_Procedure:
-		os.write_string(fd, "proc");
+		print_string(fd, "proc");
 		if info.params == nil {
-			os.write_string(fd, "()");
+			print_string(fd, "()");
 		} else {
 			t := info.params.variant.(Type_Info_Tuple);
-			os.write_byte(fd, '(');
+			print_byte(fd, '(');
 			for t, i in t.types {
-				if i > 0 do os.write_string(fd, ", ");
+				if i > 0 do print_string(fd, ", ");
 				print_type(fd, t);
 			}
-			os.write_string(fd, ")");
+			print_string(fd, ")");
 		}
 		if info.results != nil {
-			os.write_string(fd, " -> ");
+			print_string(fd, " -> ");
 			print_type(fd, info.results);
 		}
 	case Type_Info_Tuple:
 		count := len(info.names);
-		if count != 1 do os.write_byte(fd, '(');
+		if count != 1 do print_byte(fd, '(');
 		for name, i in info.names {
-			if i > 0 do os.write_string(fd, ", ");
+			if i > 0 do print_string(fd, ", ");
 
 			t := info.types[i];
 
 			if len(name) > 0 {
-				os.write_string(fd, name);
-				os.write_string(fd, ": ");
+				print_string(fd, name);
+				print_string(fd, ": ");
 			}
 			print_type(fd, t);
 		}
-		if count != 1 do os.write_string(fd, ")");
+		if count != 1 do print_string(fd, ")");
 
 	case Type_Info_Array:
-		os.write_byte(fd, '[');
+		print_byte(fd, '[');
 		print_u64(fd, u64(info.count));
-		os.write_byte(fd, ']');
+		print_byte(fd, ']');
 		print_type(fd, info.elem);
 
 	case Type_Info_Enumerated_Array:
-		os.write_byte(fd, '[');
+		print_byte(fd, '[');
 		print_type(fd, info.index);
-		os.write_byte(fd, ']');
+		print_byte(fd, ']');
 		print_type(fd, info.elem);
 
 
 	case Type_Info_Dynamic_Array:
-		os.write_string(fd, "[dynamic]");
+		print_string(fd, "[dynamic]");
 		print_type(fd, info.elem);
 	case Type_Info_Slice:
-		os.write_string(fd, "[]");
+		print_string(fd, "[]");
 		print_type(fd, info.elem);
 
 	case Type_Info_Map:
-		os.write_string(fd, "map[");
+		print_string(fd, "map[");
 		print_type(fd, info.key);
-		os.write_byte(fd, ']');
+		print_byte(fd, ']');
 		print_type(fd, info.value);
 
 	case Type_Info_Struct:
 		switch info.soa_kind {
 		case .None: // Ignore
 		case .Fixed:
-			os.write_string(fd, "#soa[");
+			print_string(fd, "#soa[");
 			print_u64(fd, u64(info.soa_len));
-			os.write_byte(fd, ']');
+			print_byte(fd, ']');
 			print_type(fd, info.soa_base_type);
 			return;
 		case .Slice:
-			os.write_string(fd, "#soa[]");
+			print_string(fd, "#soa[]");
 			print_type(fd, info.soa_base_type);
 			return;
 		case .Dynamic:
-			os.write_string(fd, "#soa[dynamic]");
+			print_string(fd, "#soa[dynamic]");
 			print_type(fd, info.soa_base_type);
 			return;
 		}
 
-		os.write_string(fd, "struct ");
-		if info.is_packed    do os.write_string(fd, "#packed ");
-		if info.is_raw_union do os.write_string(fd, "#raw_union ");
+		print_string(fd, "struct ");
+		if info.is_packed    do print_string(fd, "#packed ");
+		if info.is_raw_union do print_string(fd, "#raw_union ");
 		if info.custom_align {
-			os.write_string(fd, "#align ");
+			print_string(fd, "#align ");
 			print_u64(fd, u64(ti.align));
-			os.write_byte(fd, ' ');
+			print_byte(fd, ' ');
 		}
-		os.write_byte(fd, '{');
+		print_byte(fd, '{');
 		for name, i in info.names {
-			if i > 0 do os.write_string(fd, ", ");
-			os.write_string(fd, name);
-			os.write_string(fd, ": ");
+			if i > 0 do print_string(fd, ", ");
+			print_string(fd, name);
+			print_string(fd, ": ");
 			print_type(fd, info.types[i]);
 		}
-		os.write_byte(fd, '}');
+		print_byte(fd, '}');
 
 	case Type_Info_Union:
-		os.write_string(fd, "union ");
+		print_string(fd, "union ");
 		if info.custom_align {
-			os.write_string(fd, "#align ");
+			print_string(fd, "#align ");
 			print_u64(fd, u64(ti.align));
 		}
 		if info.no_nil {
-			os.write_string(fd, "#no_nil ");
+			print_string(fd, "#no_nil ");
 		}
-		os.write_byte(fd, '{');
+		print_byte(fd, '{');
 		for variant, i in info.variants {
-			if i > 0 do os.write_string(fd, ", ");
+			if i > 0 do print_string(fd, ", ");
 			print_type(fd, variant);
 		}
-		os.write_string(fd, "}");
+		print_string(fd, "}");
 
 	case Type_Info_Enum:
-		os.write_string(fd, "enum ");
+		print_string(fd, "enum ");
 		print_type(fd, info.base);
-		os.write_string(fd, " {");
+		print_string(fd, " {");
 		for name, i in info.names {
-			if i > 0 do os.write_string(fd, ", ");
-			os.write_string(fd, name);
+			if i > 0 do print_string(fd, ", ");
+			print_string(fd, name);
 		}
-		os.write_string(fd, "}");
+		print_string(fd, "}");
 
 	case Type_Info_Bit_Field:
-		os.write_string(fd, "bit_field ");
+		print_string(fd, "bit_field ");
 		if ti.align != 1 {
-			os.write_string(fd, "#align ");
+			print_string(fd, "#align ");
 			print_u64(fd, u64(ti.align));
-			os.write_byte(fd, ' ');
+			print_byte(fd, ' ');
 		}
-		os.write_string(fd, " {");
+		print_string(fd, " {");
 		for name, i in info.names {
-			if i > 0 do os.write_string(fd, ", ");
-			os.write_string(fd, name);
-			os.write_string(fd, ": ");
+			if i > 0 do print_string(fd, ", ");
+			print_string(fd, name);
+			print_string(fd, ": ");
 			print_u64(fd, u64(info.bits[i]));
 		}
-		os.write_string(fd, "}");
+		print_string(fd, "}");
 
 	case Type_Info_Bit_Set:
-		os.write_string(fd, "bit_set[");
+		print_string(fd, "bit_set[");
 
 		#partial switch elem in type_info_base(info.elem).variant {
 		case Type_Info_Enum:
 			print_type(fd, info.elem);
 		case Type_Info_Rune:
-			os.write_encoded_rune(fd, rune(info.lower));
-			os.write_string(fd, "..");
-			os.write_encoded_rune(fd, rune(info.upper));
+			print_encoded_rune(fd, rune(info.lower));
+			print_string(fd, "..");
+			print_encoded_rune(fd, rune(info.upper));
 		case:
 			print_i64(fd, info.lower);
-			os.write_string(fd, "..");
+			print_string(fd, "..");
 			print_i64(fd, info.upper);
 		}
 		if info.underlying != nil {
-			os.write_string(fd, "; ");
+			print_string(fd, "; ");
 			print_type(fd, info.underlying);
 		}
-		os.write_byte(fd, ']');
+		print_byte(fd, ']');
 
 	case Type_Info_Opaque:
-		os.write_string(fd, "opaque ");
+		print_string(fd, "opaque ");
 		print_type(fd, info.elem);
 
 	case Type_Info_Simd_Vector:
 		if info.is_x86_mmx {
-			os.write_string(fd, "intrinsics.x86_mmx");
+			print_string(fd, "intrinsics.x86_mmx");
 		} else {
-			os.write_string(fd, "#simd[");
+			print_string(fd, "#simd[");
 			print_u64(fd, u64(info.count));
-			os.write_byte(fd, ']');
+			print_byte(fd, ']');
 			print_type(fd, info.elem);
 		}
 
 	case Type_Info_Relative_Pointer:
-		os.write_string(fd, "#relative(");
+		print_string(fd, "#relative(");
 		print_type(fd, info.base_integer);
-		os.write_string(fd, ") ");
+		print_string(fd, ") ");
 		print_type(fd, info.pointer);
 
 	case Type_Info_Relative_Slice:
-		os.write_string(fd, "#relative(");
+		print_string(fd, "#relative(");
 		print_type(fd, info.base_integer);
-		os.write_string(fd, ") ");
+		print_string(fd, ") ");
 		print_type(fd, info.slice);
 	}
 }