Removed the use of temp allocator in backtrace printing.

Added string::bformat.
This commit is contained in:
Christoffer Lerno
2025-07-13 02:38:45 +02:00
committed by Christoffer Lerno
parent 6c7dc2a28e
commit e755c36ea2
13 changed files with 272 additions and 150 deletions

View File

@@ -98,41 +98,34 @@ macro anycast(any v, $Type) @builtin
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE) => @stack_mem(0x1100; Allocator smem)
{
Allocator t = allocator::current_temp;
TempAllocator* t2 = allocator::top_temp;
TempAllocator* new_t = allocator::new_temp_allocator(smem, 0x1000)!!;
allocator::current_temp = allocator::top_temp = new_t;
defer
{
allocator::current_temp = t;
allocator::top_temp = t2;
new_t.free();
}
void*[256] buffer;
void*[] backtraces = backtrace::capture_current(&buffer);
backtraces_to_ignore++;
BacktraceList? backtrace = backtrace::symbolize_backtrace(tmem, backtraces);
if (catch backtrace) return false;
if (backtrace.len() <= backtraces_to_ignore) return false;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
@stack_mem(2048; Allocator mem)
{
if (i < backtraces_to_ignore) continue;
String inline_suffix = trace.is_inline ? " [inline]" : "";
if (trace.is_unknown())
BacktraceList? backtrace = backtrace::symbolize_backtrace(mem, backtraces);
if (catch backtrace) return false;
if (backtrace.len() <= backtraces_to_ignore) return false;
io::eprint("\nERROR: '");
io::eprint(message);
io::eprintn("'");
foreach (i, &trace : backtrace)
{
io::eprintfn(" in ???%s", inline_suffix);
continue;
if (i < backtraces_to_ignore) continue;
String inline_suffix = trace.is_inline ? " [inline]" : "";
if (trace.is_unknown())
{
io::eprintfn(" in ???%s", inline_suffix);
continue;
}
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
}
if (trace.has_file())
{
io::eprintfn(" in %s (%s:%d) [%s]%s", trace.function, trace.file, trace.line, trace.object_file, inline_suffix);
continue;
}
io::eprintfn(" in %s (source unavailable) [%s]%s", trace.function, trace.object_file, inline_suffix);
}
};
return true;
}

View File

@@ -7,6 +7,10 @@ import std::math;
const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
const DEFAULT_MEM_ALIGNMENT = (void*.alignof) * 2;
const ulong KB = 1024;
const ulong MB = KB * 1024;
const ulong GB = MB * 1024;
const ulong TB = GB * 1024;
faultdef OUT_OF_MEMORY, INVALID_ALLOC_SIZE;

View File

@@ -107,6 +107,19 @@ fn String format(Allocator allocator, String fmt, args...) @format(1) => @pool()
return str.copy_str(allocator);
}
<*
Return a new String created using the formatting function.
@param [inout] buffer : `The buffer to use`
@param [in] fmt : `The formatting string`
*>
fn String bformat(char[] buffer, String fmt, args...) @format(1)
{
DString str = dstring::new_with_capacity(allocator::wrap(buffer), fmt.len + args.len * 8);
str.appendf(fmt, ...args);
return str.str_view();
}
<*
Return a temporary String created using the formatting function.

View File

@@ -115,10 +115,10 @@ fn bool clear_var(String name) => @pool()
$endswitch
}
fn String? executable_path(Allocator allocator)
fn String? executable_path()
{
$if env::DARWIN:
return darwin::executable_path(allocator);
return darwin::executable_path();
$else
return NOT_FOUND?;
$endif

View File

@@ -130,70 +130,76 @@ fn ulong? elf_module_image_base(String path) @local
fn void? backtrace_add_from_exec(Allocator allocator, BacktraceList* list, void* addr) @local
{
char[] buf = mem::talloc_array(char, 1024);
String exec_path = process::execute_stdout_to_buffer(buf, {"realpath", "-e", string::tformat("/proc/%d/exe", posix::getpid())})!;
char[1024] buf @noinit;
String exec_path = process::execute_stdout_to_buffer(&buf, {"realpath", "-e", string::bformat(&&(char[64]){}, "/proc/%d/exe", posix::getpid())})!;
String obj_name = exec_path.copy(allocator);
String addr2line = process::execute_stdout_to_buffer(buf, {"addr2line", "-p", "-i", "-C", "-f", "-e", exec_path, string::tformat("0x%x", addr)})!;
String addr2line = process::execute_stdout_to_buffer(&buf, {"addr2line", "-p", "-i", "-C", "-f", "-e", exec_path, string::bformat(&&(char[64]){}, "0x%x", addr)})!;
return backtrace_add_addr2line(allocator, list, addr, addr2line, obj_name, "???");
}
fn void? backtrace_add_from_dlinfo(Allocator allocator, BacktraceList* list, void* addr, Linux_Dl_info* info) @local
{
char[] buf = mem::talloc_array(char, 1024);
char[1024] buf @noinit;
void* obj_addr = addr - (uptr)info.dli_fbase + (uptr)elf_module_image_base(info.dli_fname.str_view())!;
ZString obj_path = info.dli_fname;
String sname = info.dli_sname ? info.dli_sname.str_view() : "???";
String addr2line = process::execute_stdout_to_buffer(buf, {"addr2line", "-p", "-i", "-C", "-f", "-e", obj_path.str_view(), string::tformat("0x%x", obj_addr - 1)})!;
String addr2line = process::execute_stdout_to_buffer(&buf, {"addr2line", "-p", "-i", "-C", "-f", "-e", obj_path.str_view(), string::bformat(&&(char[64]){}, "0x%x", obj_addr - 1)})!;
return backtrace_add_addr2line(allocator, list, addr, addr2line, info.dli_fname.str_view(), sname);
}
fn Backtrace? backtrace_line_parse(Allocator allocator, String string, String obj_name, String func_name, bool is_inlined)
{
String[] parts = string.trim().tsplit(" at ");
if (parts.len != 2) return NOT_FOUND?;
uint line = 0;
String source = "";
if (!parts[1].contains("?") && parts[1].contains(":"))
@stack_mem(256; Allocator mem)
{
usz index = parts[1].rindex_of_char(':')!;
source = parts[1][:index];
line = parts[1][index + 1..].to_uint()!;
}
return {
.function = parts[0].copy(allocator),
.object_file = obj_name.copy(allocator),
.file = source.copy(allocator),
.line = line,
.allocator = allocator,
.is_inline = is_inlined
String[] parts = string.trim().split(mem, " at ");
if (parts.len != 2) return NOT_FOUND?;
uint line = 0;
String source = "";
if (!parts[1].contains("?") && parts[1].contains(":"))
{
usz index = parts[1].rindex_of_char(':')!;
source = parts[1][:index];
line = parts[1][index + 1..].to_uint()!;
}
return {
.function = parts[0].copy(allocator),
.object_file = obj_name.copy(allocator),
.file = source.copy(allocator),
.line = line,
.allocator = allocator,
.is_inline = is_inlined
};
};
}
fn void? backtrace_add_addr2line(Allocator allocator, BacktraceList* list, void* addr, String addr2line, String obj_name, String func_name) @local
{
String[] inline_parts = addr2line.tsplit("(inlined by)");
usz last = inline_parts.len - 1;
foreach (i, part : inline_parts)
@stack_mem(1024; Allocator mem)
{
bool is_inline = i != last;
Backtrace? trace = backtrace_line_parse(allocator, part, obj_name, func_name, is_inline);
if (catch trace)
String[] inline_parts = addr2line.split(mem, "(inlined by)");
usz last = inline_parts.len - 1;
foreach (i, part : inline_parts)
{
list.push({
.function = func_name.copy(allocator),
.object_file = obj_name.copy(allocator),
.offset = (uptr)addr,
.file = "".copy(allocator),
.line = 0,
.allocator = allocator,
.is_inline = is_inline
});
continue;
bool is_inline = i != last;
Backtrace? trace = backtrace_line_parse(allocator, part, obj_name, func_name, is_inline);
if (catch trace)
{
list.push({
.function = func_name.copy(allocator),
.object_file = obj_name.copy(allocator),
.offset = (uptr)addr,
.file = "".copy(allocator),
.line = 0,
.allocator = allocator,
.is_inline = is_inline
});
continue;
}
list.push(trace);
}
list.push(trace);
}
};
}
fn void? backtrace_add_element(Allocator allocator, BacktraceList *list, void* addr) @local
@@ -204,15 +210,12 @@ fn void? backtrace_add_element(Allocator allocator, BacktraceList *list, void* a
return;
}
@pool()
Linux_Dl_info info;
if (dladdr(addr, &info) == 0)
{
Linux_Dl_info info;
if (dladdr(addr, &info) == 0)
{
return backtrace_add_from_exec(allocator, list, addr);
}
return backtrace_add_from_dlinfo(allocator, list, addr, &info);
};
return backtrace_add_from_exec(allocator, list, addr);
}
return backtrace_add_from_dlinfo(allocator, list, addr, &info);
}
fn BacktraceList? symbolize_backtrace(Allocator allocator, void*[] backtrace)
@@ -227,12 +230,9 @@ fn BacktraceList? symbolize_backtrace(Allocator allocator, void*[] backtrace)
}
list.free();
}
@pool()
foreach (addr : backtrace)
{
foreach (addr : backtrace)
{
backtrace_add_element(allocator, &list, addr)!;
}
};
backtrace_add_element(allocator, &list, addr)!;
}
return list;
}

View File

@@ -79,19 +79,26 @@ alias Darwin_mach_timebase_info_data_t = Darwin_mach_timebase_info;
extern fn void mach_timebase_info(Darwin_mach_timebase_info_data_t* timebase);
extern fn ulong mach_absolute_time();
fn String? executable_path(Allocator allocator)
fn String? executable_path()
{
char[4096] path;
uint len = path.len;
if (darwin_NSGetExecutablePath(&path, &len) < 0) return NOT_FOUND?;
return ((ZString)&path).copy(allocator);
static char[4096] path;
static uint len = 0;
if (!len)
{
char[4096] buf;
uint temp_len = buf.len;
if (darwin_NSGetExecutablePath(&buf, &temp_len) < 0) return NOT_FOUND?;
path[:temp_len] = buf[:temp_len];
len = temp_len;
}
return (String)path[:len];
}
fn uptr? load_address() @local
{
Darwin_segment_command_64* cmd = darwin::getsegbyname("__TEXT");
if (!cmd) return backtrace::SEGMENT_NOT_FOUND?;
String path = env::executable_path(tmem) ?? backtrace::EXECUTABLE_PATH_NOT_FOUND?!;
String path = env::executable_path() ?? backtrace::EXECUTABLE_PATH_NOT_FOUND?!;
uint dyld_count = darwin::_dyld_image_count();
for (uint i = 0; i < dyld_count; i++)
{
@@ -103,23 +110,22 @@ fn uptr? load_address() @local
return backtrace::IMAGE_NOT_FOUND?;
}
fn Backtrace? backtrace_load_element(Allocator allocator, String execpath, void* buffer, void* load_address) @local
{
@pool()
if (buffer)
{
if (buffer)
{
char* buf = tmalloc(1024);
String s = process::execute_stdout_to_buffer(buf[:1024],
{ "atos", "-o", execpath, "-arch", env::AARCH64 ? "arm64" : "x86_64", "-l",
string::tformat("%p", load_address),
string::tformat("%p", buffer - 1),
"-fullPath" })!;
String[] parts = s.tsplit(" ");
char[1024] buf;
String s = process::execute_stdout_to_buffer(&buf,
{ "atos", "-o", execpath, "-arch", env::AARCH64 ? "arm64" : "x86_64", "-l",
string::bformat(&&(char[64]){}, "%p", load_address),
string::bformat(&&(char[64]){}, "%p", buffer - 1),
"-fullPath" })!;
@stack_mem(512; Allocator mem)
{
String[] parts = s.split(mem, " ", 5);
if (parts.len == 4)
{
String[] path_parts = parts[3].tsplit(":");
String[] path_parts = parts[3].split(mem, ":");
return {
.offset = (uptr)buffer,
.function = parts[0].copy(allocator),
@@ -129,17 +135,17 @@ fn Backtrace? backtrace_load_element(Allocator allocator, String execpath, void*
.allocator = allocator
};
}
}
Darwin_Dl_info info;
if (!buffer || !darwin::dladdr(buffer, &info)) return backtrace::BACKTRACE_UNKNOWN;
return {
.offset = (uptr)buffer,
.function = info.dli_sname ? info.dli_sname.copy(allocator) : "???".copy(allocator),
.object_file = info.dli_fname.copy(allocator),
.file = "".copy(allocator),
.line = 0,
.allocator = allocator
};
}
Darwin_Dl_info info;
if (!buffer || !darwin::dladdr(buffer, &info)) return backtrace::BACKTRACE_UNKNOWN;
return {
.offset = (uptr)buffer,
.function = info.dli_sname ? info.dli_sname.copy(allocator) : "???".copy(allocator),
.object_file = info.dli_fname.copy(allocator),
.file = "".copy(allocator),
.line = 0,
.allocator = allocator
};
}
@@ -156,14 +162,11 @@ fn BacktraceList? symbolize_backtrace(Allocator allocator, void*[] backtrace)
}
list.free();
}
@pool()
String execpath = executable_path()!;
foreach (addr : backtrace)
{
String execpath = executable_path(tmem)!;
foreach (addr : backtrace)
{
list.push(backtrace_load_element(allocator, execpath, addr, load_addr) ?? backtrace::BACKTRACE_UNKNOWN);
}
};
list.push(backtrace_load_element(allocator, execpath, addr, load_addr) ?? backtrace::BACKTRACE_UNKNOWN);
}
return list;
}

View File

@@ -1,3 +1,8 @@
module std::os::posix @if(env::POSIX);
extern ZString* environ;
extern fn CLong sysconf(CInt name);
const CInt _SC_PAGESIZE @if(env::LINUX) = 30;
const CInt _SC_PAGESIZE @if(env::DARWIN) = 29;

28
lib/std/os/posix/mman.c3 Normal file
View File

@@ -0,0 +1,28 @@
module std::os::posix @if(env::POSIX);
import libc;
const PROT_NONE = 0x00; // no permissions
const PROT_READ = 0x01; // pages can be read
const PROT_WRITE = 0x02; // pages can be written
const PROT_EXEC = 0x04; // pages can be executed
const MAP_SHARED = 0x0001; // share changes
const MAP_PRIVATE = 0x0002; // changes are private
const MAP_FILE = 0x0000; // map from file (default)
const MAP_ANONYMOUS = 0x1000; // allocated from memory, swap space
const void* MAP_FAILED = (void *)(uptr)-1; // mmap failed
const MADV_NORMAL = 0; // no further special treatment
const MADV_RANDOM = 1; // expect random page refs
const MADV_SEQUENTIAL = 2; // expect sequential page refs
const MADV_WILLNEED = 3; // will need these pages
const MADV_DONTNEED = 4; // dont need these pages
extern fn void* mmap(void*, usz, CInt, CInt, CInt, Off_t);
extern fn CInt munmap(void*, usz);
extern fn CInt mprotect(void*, usz, CInt);
extern fn int madvise(void*, usz, CInt);
extern fn CInt getpagesize();

View File

@@ -55,21 +55,18 @@ fn void? create_named_pipe_helper(void** rd, void **wr) @local @if(env::WIN32)
tlocal long index = 0;
long unique = index++;
@pool()
{
String s = string::tformat(`\\.\pipe\c3_subprocess.%08x.%08x.%d`, win32::getCurrentProcessId(), win32::getCurrentThreadId(), unique);
Win32_LPCSTR str = (Win32_LPCSTR)s.ptr;
*rd = win32::createNamedPipeA(
str,
win32::PIPE_ACCESS_INBOUND | win32::FILE_FLAG_OVERLAPPED,
win32::PIPE_TYPE_BYTE | win32::PIPE_WAIT,
1, 4096, 4096, 0, &sa_attr);
if (win32::INVALID_HANDLE_VALUE == *rd) return FAILED_TO_CREATE_PIPE?;
*wr = win32::createFileA(
str, win32::GENERIC_WRITE, 0, &sa_attr,
win32::OPEN_EXISTING, win32::FILE_ATTRIBUTE_NORMAL, null);
if (win32::INVALID_HANDLE_VALUE == *wr) return FAILED_TO_CREATE_PIPE?;
};
String s = string::bformat(&&(char[128]){}, `\\.\pipe\c3_subprocess.%08x.%08x.%d`, win32::getCurrentProcessId(), win32::getCurrentThreadId(), unique);
Win32_LPCSTR str = (Win32_LPCSTR)s.ptr;
*rd = win32::createNamedPipeA(
str,
win32::PIPE_ACCESS_INBOUND | win32::FILE_FLAG_OVERLAPPED,
win32::PIPE_TYPE_BYTE | win32::PIPE_WAIT,
1, 4096, 4096, 0, &sa_attr);
if (win32::INVALID_HANDLE_VALUE == *rd) return FAILED_TO_CREATE_PIPE?;
*wr = win32::createFileA(
str, win32::GENERIC_WRITE, 0, &sa_attr,
win32::OPEN_EXISTING, win32::FILE_ATTRIBUTE_NORMAL, null);
if (win32::INVALID_HANDLE_VALUE == *wr) return FAILED_TO_CREATE_PIPE?;
}
fn WString convert_command_line_win32(String[] command_line) @inline @if(env::WIN32) @local
@@ -143,12 +140,12 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
if (!win32::setHandleInformation(wr, win32::HANDLE_FLAG_INHERIT, 0)) return FAILED_TO_CREATE_PIPE?;
}
@pool()
@stack_mem(2048; Allocator mem)
{
WString used_environment = null;
if (!options.inherit_environment)
{
DString env = dstring::temp_with_capacity(64);
DString env = dstring::new_with_capacity(mem, 64);
if (!environment.len)
{
env.append("\0");
@@ -159,7 +156,7 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
env.append("\0");
}
env.append("\0");
used_environment = env.str_view().to_temp_wstring()!;
used_environment = env.str_view().to_wstring(mem)!;
}
// Handle stdin pipe if not inheriting
@@ -266,26 +263,26 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
<*
@require command_line.len > 0
*>
fn ZString* tcopy_command_line(String[] command_line) @local @inline @if(env::POSIX)
fn ZString* copy_command_line(Allocator mem, String[] command_line) @local @inline @if(env::POSIX)
{
ZString* copy = mem::talloc_array(ZString, command_line.len + 1);
ZString* copy = allocator::alloc_array(mem, ZString, command_line.len + 1);
foreach (i, str : command_line)
{
copy[i] = str.zstr_tcopy();
copy[i] = str.zstr_copy(mem);
}
copy[command_line.len] = null;
return copy;
}
const ZString[1] EMPTY_ENVIRONMENT @if(env::POSIX) = { null };
fn ZString* tcopy_env(String[] environment) @local @inline @if(env::POSIX)
fn ZString* copy_env(Allocator mem, String[] environment) @local @inline @if(env::POSIX)
{
if (!environment.len) return &EMPTY_ENVIRONMENT;
ZString* copy = mem::talloc_array(ZString, environment.len + 1);
ZString* copy = allocator::alloc_array(mem, ZString, environment.len + 1);
copy[environment.len] = null;
foreach (i, str : environment)
{
copy[i] = str.zstr_tcopy();
copy[i] = str.zstr_copy(mem);
}
return copy;
}
@@ -338,10 +335,10 @@ fn SubProcess? create(String[] command_line, SubProcessOptions options = {}, Str
}
Pid_t child;
@pool()
@stack_mem(2048; Allocator mem)
{
ZString* command_line_copy = tcopy_command_line(command_line);
ZString* used_environment = options.inherit_environment ? posix::environ : tcopy_env(environment);
ZString* command_line_copy = copy_command_line(mem, command_line);
ZString* used_environment = options.inherit_environment ? posix::environ : copy_env(mem, environment);
if (options.search_user_path)
{
if (posix::spawnp(&child, command_line_copy[0], &actions, null, command_line_copy, used_environment)) return FAILED_TO_START_PROCESS?;

View File

@@ -0,0 +1,41 @@
module std::os::win32 @if(env::WIN32);
enum Win32_AllocationType : const Win32_DWORD
{
MEM_COMMIT = 0x00001000,
MEM_RESERVE = 0x00002000,
MEM_RESET = 0x00080000,
MEM_RESET_UNDO = 0x01000000,
MEM_LARGE_PAGES = 0x20000000,
MEM_PHYSICAL = 0x00400000,
MEM_TOP_DOWN = 0x00100000,
MEM_WRITE_WATCH = 0x00200000
}
enum Win32_Protect : const Win32_DWORD
{
PAGE_EXECUTE = 0x10,
PAGE_EXECUTE_READ = 0x20,
PAGE_EXECUTE_READWRITE = 0x40,
PAGE_EXECUTE_WRITECOPY = 0x80,
PAGE_NOACCESS = 0x01,
PAGE_READONLY = 0x02,
PAGE_READWRITE = 0x04,
PAGE_WRITECOPY = 0x08,
PAGE_TARGETS_INVALID = 0x40000000,
PAGE_TARGETS_NO_UPDATE = 0x40000000,
PAGE_GUARD = 0x100,
PAGE_NOCACHE = 0x200,
PAGE_WRITECOMBINE = 0x400,
}
enum Win32_FreeType : const Win32_DWORD
{
MEM_DECOMMIT = 0x00004000,
MEM_RELEASE = 0x00008000,
MEM_COALESCE_PLACEHOLDERS = 0x00000001,
MEM_PRESERVE_PLACEHOLDER = 0x00000002,
}
extern fn Win32_LPVOID virtualAlloc(Win32_LPVOID lpAddres, Win32_SIZE_T dwSize, Win32_AllocationType flAllocationType, Win32_DWORD flProtect) @extern("VirtualAlloc");
extern fn Win32_PVOID virtualAlloc2(Win32_HANDLE process, Win32_PVOID baseAddress, Win32_SIZE_T size, Win32_AllocationType allocationType, Win32_ULONG pageProtection, Win32_MEM_EXTENDED_PARAMETER* extendedParameters, Win32_ULONG parameterCount) @extern("VirtualAlloc2");
extern fn Win32_BOOL virtualFree(Win32_LPVOID lpAddress, Win32_SIZE_T dwSize, Win32_FreeType dwFreeType) @extern("VirtualFree");

37
lib/std/os/win32/winnt.c3 Normal file
View File

@@ -0,0 +1,37 @@
module std::os::win32 @if(env::WIN32);
enum Win32_MEM_EXTENDED_PARAMETER_TYPE : CInt
{
INVALID_TYPE,
ADDRESS_REQUIREMENTS,
NUMA_NODE,
PARTITION_HANDLE,
USER_PHYSICAL_HANDLE,
ATTRIBUTE_FLAGS,
IMAGE_MACHINE,
MAX
}
alias Win32_PMEM_EXTENDED_PARAMETER_TYPE = Win32_MEM_EXTENDED_PARAMETER_TYPE;
enum Win32_MEM_EXTENDED_PARAMETER_ATTRIBUTE : const Win32_DWORD64
{
NONPAGED = 0x02, // The allocation is non-pageable.
NONPAGED_LARGE = 0x08, // The allocation is mapped using large pages.
NONPAGED_HUGE = 0x10, // The allocation is mapped using huge pages.
EC_CODE = 0x40, // The allocation will contain emulation-compatible (EC) code.
}
struct Win32_MEM_EXTENDED_PARAMETER
{
Win32_MEM_EXTENDED_PARAMETER_TYPE type;
union
{
Win32_MEM_EXTENDED_PARAMETER_ATTRIBUTE attribute; // If type is ATTRIBUTE_FLAGS
Win32_DWORD64 nodeNumber; // If type is NUMA_NODE
Win32_PVOID pointer; // If type is ADDRESS_REQUIREMENTS
Win32_SIZE_T size;
Win32_HANDLE handle;
Win32_DWORD uLong;
}
}
alias Win32_PMEM_EXTENDED_PARAMETER = Win32_MEM_EXTENDED_PARAMETER*;

View File

@@ -17,6 +17,7 @@
- Update error message for struct initialization #2286
- `$is_const` is deprecated in favour of `@is_const` based on `$defined`.
- Multiline contract comments #2113
- Removed the use of temp allocator in backtrace printing.
### Fixes
- mkdir/rmdir would not work properly with substring paths on non-windows platforms.
@@ -52,6 +53,7 @@
### Stdlib changes
- Improve contract for readline. #2280
- Added Whirlpool hash.
- Added string::bformat.
## 0.7.3 Change list

View File

@@ -681,8 +681,7 @@ no_match: ; preds = %compare
!72 = !DILocation(line: 35, column: 49, scope: !66)
!73 = !DILocalVariable(name: "name", arg: 3, scope: !66, file: !7, line: 35, type: !39)
!74 = !DILocation(line: 35, column: 63, scope: !66)
!75 = !DILocation(line: 709, column: 18, scope: !76, inlinedAt: !78)
!76 = distinct !DISubprogram(name: "new", linkageName: "new", scope: !77, file: !77, line: 706, scopeLine: 706, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition, unit: !6)
!76 = distinct !DISubprogram(name: "new", linkageName: "new", scope: !77, file: !77, line: 710, scopeLine: 710, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition, unit: !6)
!77 = !DIFile(filename: "mem.c3", directory:
!78 = !DILocation(line: 37, column: 9, scope: !66)
!79 = distinct !DISubprogram(name: "test", linkageName: "test.test", scope: !7, file: !7, line: 45, type: !80, scopeLine: 45, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !6, retainedNodes: !21)